From c321659dca5a3a10853be24f90d628eb2334e248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Stolin=CC=81ski?= Date: Tue, 8 Feb 2022 15:03:19 +0100 Subject: [PATCH 01/35] if the message is not in the main messages list, try to find in the threads messages --- packages/stream_chat/lib/src/client/channel.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index d3900842..4e5f6691 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1694,7 +1694,10 @@ class ChannelClientState { void _listenReactionDeleted() { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads.values + .expand((e) => e) + .firstWhereOrNull((e) => e.id == event.message?.id); final reaction = event.reaction; final ownReactions = oldMessage?.ownReactions ?.whereNot((it) => @@ -1714,7 +1717,10 @@ class ChannelClientState { void _listenReactions() { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads.values + .expand((e) => e) + .firstWhereOrNull((e) => e.id == event.message?.id); final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); From 9a4677a7b852c34344722455a64a20d5af462241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Stolin=CC=81ski?= Date: Tue, 8 Feb 2022 15:47:42 +0100 Subject: [PATCH 02/35] decrease parent message reply count only once --- .../stream_chat/lib/src/client/channel.dart | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index d3900842..84f0321e 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1083,7 +1083,7 @@ class Channel { } else { // remove the passed message if response does // not contain message - state!.removeMessage(message); + state!.removeMessage(message, decreaseReplyCount: true); await _client.chatPersistenceClient?.deleteMessageById(messageId); } return res; @@ -1752,6 +1752,7 @@ class ChannelClientState { _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { final message = event.message!; if (event.hardDelete == true) { + //do not decrease reply count here - it is done in _listenMessageUpdated removeMessage(message, hardDelete: true); } else { addMessage(message); @@ -1810,24 +1811,30 @@ class ChannelClientState { } /// Remove a [message] from this [channelState]. - void removeMessage(Message message, {bool hardDelete = false}) { + void removeMessage( + Message message, { + bool hardDelete = false, + bool decreaseReplyCount = false, + }) { final parentId = message.parentId; // i.e. it's a thread message // 1. Remove the thread message // 2. Reduce total reply count of parent message if (parentId != null) { - final allMessages = [...messages]; - final parentMessage = allMessages.firstWhereOrNull( - (it) => it.id == parentId, - ); + if (decreaseReplyCount) { + final allMessages = [...messages]; + final parentMessage = allMessages.firstWhereOrNull( + (it) => it.id == parentId, + ); - // return if message not available in the memory - if (parentMessage == null) return; - final replyCount = parentMessage.replyCount; - // return if reply count is null or zero - if (replyCount == null || replyCount == 0) return; + // return if message not available in the memory + if (parentMessage == null) return; + final replyCount = parentMessage.replyCount; + // return if reply count is null or zero + if (replyCount == null || replyCount == 0) return; - addMessage(parentMessage.copyWith(replyCount: replyCount - 1)); + addMessage(parentMessage.copyWith(replyCount: replyCount - 1)); + } updateThreadInfo( parentId, threads[parentId]! From c49641fde7eec5794a57c11f5204282bf4372f62 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 14 Feb 2022 19:52:50 +0530 Subject: [PATCH 03/35] test(llc): fix markAllRead test Signed-off-by: xsahil03x --- packages/stream_chat/test/src/core/api/channel_api_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/test/src/core/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart index 7835ab6a..e631fdb2 100644 --- a/packages/stream_chat/test/src/core/api/channel_api_test.dart +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -175,14 +175,14 @@ void main() { test('markAllRead', () async { const path = '/channels/read'; - when(() => client.post(path)).thenAnswer( + when(() => client.post(path, data: {})).thenAnswer( (_) async => successResponse(path, data: {})); final res = await channelApi.markAllRead(); expect(res, isNotNull); - verify(() => client.post(path)).called(1); + verify(() => client.post(path, data: {})).called(1); verifyNoMoreInteractions(client); }); From 338b4ac6198b5d329a079596140e5c2a7dd35511 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 14 Feb 2022 20:04:49 +0530 Subject: [PATCH 04/35] chore(llc): fix lint Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/client/channel.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index d3900842..945574fd 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1322,7 +1322,8 @@ class Channel { /// Remove the ban for the user with given [userID] in the channel. @Deprecated( - "Use 'unbanMember' instead. This method will be removed in v4.0.0") + "Use 'unbanMember' instead. This method will be removed in v4.0.0", + ) Future unbanUser(String userID) => unbanMember(userID); /// Remove the ban for the member with given [userID] in the channel. From 6e2f7855aa951133bf465eed68c87011aeebe4da Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 14 Feb 2022 10:04:09 -0500 Subject: [PATCH 05/35] (chore:flutter) - run sample app on macos Some xcode project settings were updated due to building for macos --- .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ++-- .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj index 1721e6f1..4b1a8d5d 100644 --- a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cf..3db53b6e 100644 --- a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 15 Feb 2022 11:02:56 +0100 Subject: [PATCH 06/35] fix(llc): thread message reactions (#1) * fix: thread reactions not updated * test: thread message reactions * docs: update changelog --- packages/stream_chat/CHANGELOG.md | 6 + .../stream_chat/lib/src/client/channel.dart | 15 +- .../test/src/client/channel_test.dart | 298 ++++++++++++++++++ packages/stream_chat/test/src/matchers.dart | 7 + 4 files changed, 318 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 787aa2af..c9ffa7c1 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🐞 Fixed + +- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890). Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). +- ## 3.4.0 🐞 Fixed diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 4e5f6691..2089a52d 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1695,9 +1695,8 @@ class ChannelClientState { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { final oldMessage = messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? - threads.values - .expand((e) => e) - .firstWhereOrNull((e) => e.id == event.message?.id); + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final reaction = event.reaction; final ownReactions = oldMessage?.ownReactions ?.whereNot((it) => @@ -1718,9 +1717,8 @@ class ChannelClientState { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { final oldMessage = messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? - threads.values - .expand((e) => e) - .firstWhereOrNull((e) => e.id == event.message?.id); + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); @@ -1736,8 +1734,9 @@ class ChannelClientState { ) .listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); - + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index adbfb388..50786de0 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -1239,6 +1239,189 @@ void main() { ); }); + group('`.sendReaction in thread`', () { + test('should work fine', () async { + const type = 'test-reaction-type'; + final message = Message( + id: 'test-message-id', + parentId: 'test-parent-id', // is thread message + ); + + final reaction = Reaction(type: type, messageId: message.id); + + when(() => client.sendReaction(message.id, type)).thenAnswer( + (_) async => SendReactionResponse() + ..message = message + ..reaction = reaction, + ); + + expectLater( + channel.state?.threadsStream + // skipping first seed message list -> [] messages + .skip(1) + .map((event) => event['test-parent-id']), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: 1}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction(message, type); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, message.id); + + verify(() => client.sendReaction(message.id, type)).called(1); + }); + + test( + '''should restore previous thread message if `client.sendReaction` throws''', + () async { + const type = 'test-reaction-type'; + final message = Message( + id: 'test-message-id', + parentId: 'test-parent-id', // is thread message + ); + + final reaction = Reaction(type: type, messageId: message.id); + + when(() => client.sendReaction(message.id, type)) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.threadsStream + .skip(1) + .map((event) => event['test-parent-id']), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: 1}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + [ + isSameMessageAs( + message, + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + ]), + ); + + try { + await channel.sendReaction(message, type); + } catch (e) { + expect(e, isA()); + } + + verify(() => client.sendReaction(message.id, type)).called(1); + }, + ); + + test( + '''should override previous thread reaction if present and `enforceUnique` is true''', + () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const parentId = 'test-parent-id'; + const prevType = 'test-reaction-type'; + final prevReaction = Reaction( + type: prevType, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + parentId: parentId, + ownReactions: [prevReaction], + latestReactions: [prevReaction], + reactionScores: const {prevType: 1}, + reactionCounts: const {prevType: 1}, + ); + + const type = 'test-reaction-type-2'; + final newReaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final newMessage = message.copyWith( + ownReactions: [newReaction], + latestReactions: [newReaction], + ); + + const enforceUnique = true; + + when(() => client.sendReaction( + messageId, + type, + enforceUnique: enforceUnique, + )).thenAnswer( + (_) async => SendReactionResponse() + ..message = newMessage + ..reaction = newReaction, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.threadsStream + .skip(1) + .map((event) => event['test-parent-id']), + emitsInOrder([ + [ + isSameMessageAs( + newMessage.copyWith(status: MessageSendingStatus.sent), + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction( + message, + type, + enforceUnique: enforceUnique, + ); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, messageId); + + verify(() => client.sendReaction( + messageId, + type, + enforceUnique: enforceUnique, + )).called(1); + }, + ); + }); + group('`.deleteReaction`', () { test('should work fine', () async { const userId = 'test-user-id'; @@ -1343,6 +1526,121 @@ void main() { ); }); + group('`.deleteReaction in thread`', () { + test('should work fine', () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const parentId = 'test-parent-id'; + const type = 'test-reaction-type'; + final reaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + parentId: parentId, // is thread + ownReactions: [reaction], + latestReactions: [reaction], + reactionScores: const {type: 1}, + reactionCounts: const {type: 1}, + ); + + when(() => client.deleteReaction(messageId, type)) + .thenAnswer((_) async => EmptyResponse()); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.threadsStream + .skip(1) + .map((event) => event['test-parent-id']), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + latestReactions: [], + ownReactions: [], + ), + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + ]), + ); + + final res = await channel.deleteReaction(message, reaction); + + expect(res, isNotNull); + + verify(() => client.deleteReaction(messageId, type)).called(1); + }); + + test( + 'should restore prev message state if `client.deleteReaction` throws', + () async { + const userId = 'test-user-id'; + const messageId = 'test-message-id'; + const parentId = 'test-parent-id'; + const type = 'test-reaction-type'; + final reaction = Reaction( + type: type, + messageId: messageId, + userId: userId, + ); + final message = Message( + id: messageId, + parentId: parentId, + ownReactions: [reaction], + latestReactions: [reaction], + reactionScores: const {type: 1}, + reactionCounts: const {type: 1}, + ); + + when(() => client.deleteReaction(messageId, type)) + .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.threadsStream + .skip(1) + .map((event) => event['test-parent-id']), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + latestReactions: [], + ownReactions: [], + ), + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + [ + isSameMessageAs( + message, + matchReactions: true, + matchSendingStatus: true, + matchParentId: true, + ), + ], + ]), + ); + + try { + await channel.deleteReaction(message, reaction); + } catch (e) { + expect(e, isA()); + } + + verify(() => client.deleteReaction(messageId, type)).called(1); + }, + ); + }); + test('`.update`', () async { const channelData = { 'name': 'Stream Team', diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index e54b113d..102480cd 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -49,6 +49,7 @@ Matcher isSameMessageAs( bool matchSendingStatus = false, bool matchAttachments = false, bool matchAttachmentsUploadState = false, + bool matchParentId = false, }) => _IsSameMessageAs( targetMessage: targetMessage, @@ -57,6 +58,7 @@ Matcher isSameMessageAs( matchSendingStatus: matchSendingStatus, matchAttachments: matchAttachments, matchAttachmentsUploadState: matchAttachmentsUploadState, + matchParentId: matchParentId, ); class _IsSameMessageAs extends Matcher { @@ -67,6 +69,7 @@ class _IsSameMessageAs extends Matcher { this.matchSendingStatus = false, this.matchAttachments = false, this.matchAttachmentsUploadState = false, + this.matchParentId = false, }); final Message targetMessage; @@ -75,6 +78,7 @@ class _IsSameMessageAs extends Matcher { final bool matchSendingStatus; final bool matchAttachments; final bool matchAttachmentsUploadState; + final bool matchParentId; @override Description describe(Description description) => @@ -123,6 +127,9 @@ class _IsSameMessageAs extends Matcher { matches &= matchAttachments(); } + if (matchParentId) { + matches &= message.parentId == targetMessage.parentId; + } return matches; } } From a191e7f32517d2537d194136d4648928c9b7825d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 15 Feb 2022 16:26:41 +0530 Subject: [PATCH 07/35] fix(ui): fix `unban` command not working in `MessageInput` Signed-off-by: xsahil03x --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ .../lib/src/message_input.dart | 20 +++++++++++++++++++ .../lib/src/message_text.dart | 3 ++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index f19972b4..d68549e1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🐞 Fixed + +- [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`. + ## 3.4.0 - Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 8672f180..d1a45786 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1816,6 +1816,8 @@ class MessageInputState extends State { _mentionedUsers.clear(); + message = _replaceUserNameWithId(message); + try { Future sendingFuture; if (widget.editMessage == null || @@ -2081,3 +2083,21 @@ class _CountdownButton extends StatelessWidget { ), ); } + +Message _replaceUserNameWithId(Message message) { + final mentionedUsers = message.mentionedUsers; + if (mentionedUsers.isEmpty) return message; + + var messageTextToSend = message.text; + if (messageTextToSend == null) return message; + + for (final user in mentionedUsers.toSet()) { + final userName = user.name; + messageTextToSend = messageTextToSend!.replaceAll( + '@$userName', + '@${user.id}', + ); + } + + return message.copyWith(text: messageTextToSend); +} diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index bb2946f4..4787bb9e 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -84,9 +84,10 @@ class MessageText extends StatelessWidget { String _replaceMentions(String text) { var messageTextToRender = text; for (final user in message.mentionedUsers.toSet()) { + final userId = user.id; final userName = user.name; messageTextToRender = messageTextToRender.replaceAll( - '@$userName', + '@$userId', '[@$userName](@${userName.replaceAll(' ', '')})', ); } From 538fdd6bc58daf7ac4d0bfc0624424cf984e079f Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 12:15:55 +0530 Subject: [PATCH 08/35] Exposed score in the sendReaction function for channel --- packages/stream_chat/lib/src/client/channel.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 945574fd..fe25626e 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -811,6 +811,7 @@ class Channel { Future sendReaction( Message message, String type, { + int? score, Map extraData = const {}, bool enforceUnique = false, }) async { @@ -862,6 +863,13 @@ class Channel { state?.addMessage(newMessage); + if (score != null) { + extraData.putIfAbsent( + 'score', + () => score, + ); + } + try { final reactionResp = await _client.sendReaction( messageId, From 3fc9aabe6f0e5b5cfb7172c1a6a23f11e34b30a8 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 14:05:48 +0530 Subject: [PATCH 09/35] Exposed score for send reaction in the client side as well --- .../stream_chat/lib/src/client/channel.dart | 12 +++-------- .../stream_chat/lib/src/client/client.dart | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index fe25626e..fdd1d1d6 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -811,7 +811,7 @@ class Channel { Future sendReaction( Message message, String type, { - int? score, + int score = 1, Map extraData = const {}, bool enforceUnique = false, }) async { @@ -830,7 +830,7 @@ class Channel { createdAt: now, type: type, user: user, - score: 1, + score: score, extraData: extraData, ); @@ -863,17 +863,11 @@ class Channel { state?.addMessage(newMessage); - if (score != null) { - extraData.putIfAbsent( - 'score', - () => score, - ); - } - try { final reactionResp = await _client.sendReaction( messageId, type, + score: score, extraData: extraData, enforceUnique: enforceUnique, ); diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index a9486c08..f44f3d52 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1157,15 +1157,22 @@ class StreamChatClient { Future sendReaction( String messageId, String reactionType, { + int score = 1, Map extraData = const {}, bool enforceUnique = false, - }) => - _chatApi.message.sendReaction( - messageId, - reactionType, - extraData: extraData, - enforceUnique: enforceUnique, - ); + }) { + extraData.putIfAbsent( + 'score', + () => score, + ); + + return _chatApi.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + enforceUnique: enforceUnique, + ); + } /// Delete a [reactionType] from this [messageId] Future deleteReaction( From 6fa746fec350b8508111498bc92e17bf550828b4 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 18:51:06 +0530 Subject: [PATCH 10/35] Fixed unmodifiable const map bug and added client and channel tests --- .../stream_chat/lib/src/client/client.dart | 10 +- .../test/src/client/channel_test.dart | 129 ++++++++++++++++++ .../test/src/client/client_test.dart | 114 ++++++++++++++-- 3 files changed, 234 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index f44f3d52..9c593694 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1161,15 +1161,15 @@ class StreamChatClient { Map extraData = const {}, bool enforceUnique = false, }) { - extraData.putIfAbsent( - 'score', - () => score, - ); + final _extraData = { + 'score': score, + ...extraData, + }; return _chatApi.message.sendReaction( messageId, reactionType, - extraData: extraData, + extraData: _extraData, enforceUnique: enforceUnique, ); } diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index adbfb388..cc89c44b 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -1116,6 +1116,135 @@ void main() { verify(() => client.sendReaction(message.id, type)).called(1); }); + test('should work fine with score passed explicitly', () async { + const type = 'test-reaction-type'; + final message = Message(id: 'test-message-id'); + + const score = 5; + final reaction = Reaction( + type: type, + messageId: message.id, + score: score, + ); + + when(() => client.sendReaction( + message.id, + type, + score: score, + )).thenAnswer( + (_) async => SendReactionResponse() + ..message = message + ..reaction = reaction, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: score}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction( + message, + type, + score: score, + ); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, message.id); + expect(res.reaction.score, score); + + verify(() => client.sendReaction( + message.id, + type, + score: score, + )).called(1); + }); + + test('should work fine with score passed explicitly and in extraData', + () async { + const type = 'test-reaction-type'; + final message = Message(id: 'test-message-id'); + + const score = 5; + const extraDataScore = 3; + const extraData = { + 'score': extraDataScore, + }; + final reaction = Reaction( + type: type, + messageId: message.id, + score: extraDataScore, + ); + + when(() => client.sendReaction( + message.id, + type, + score: score, + extraData: extraData, + )).thenAnswer( + (_) async => SendReactionResponse() + ..message = message + ..reaction = reaction, + ); + + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + reactionCounts: {type: 1}, + reactionScores: {type: extraDataScore}, + latestReactions: [reaction], + ownReactions: [reaction], + ), + matchReactions: true, + matchSendingStatus: true, + ), + ], + ]), + ); + + final res = await channel.sendReaction( + message, + type, + score: score, + extraData: extraData, + ); + + expect(res, isNotNull); + expect(res.reaction.type, type); + expect(res.reaction.messageId, message.id); + expect( + res.reaction.score, + extraDataScore, + ); + + verify(() => client.sendReaction( + message.id, + type, + score: score, + extraData: extraData, + )).called(1); + }); + test( 'should restore previous message if `client.sendReaction` throws', () async { diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index b89db0dc..c9aa8968 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -1956,23 +1956,109 @@ void main() { verifyNoMoreInteractions(api.channel); }); - test('`.sendReaction`', () async { - const messageId = 'test-message-id'; - const reactionType = 'like'; + group('`.sendReaction`', () { + test('`.sendReaction with default params`', () async { + const messageId = 'test-message-id'; + const reactionType = 'like'; + const extraData = {'score': 1}; - when(() => api.message.sendReaction(messageId, reactionType)) - .thenAnswer((_) async => SendReactionResponse() - ..message = Message(id: messageId) - ..reaction = Reaction(type: reactionType, messageId: messageId)); + when(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).thenAnswer((_) async => SendReactionResponse() + ..message = Message(id: messageId) + ..reaction = Reaction(type: reactionType, messageId: messageId)); - final res = await client.sendReaction(messageId, reactionType); - expect(res, isNotNull); - expect(res.message.id, messageId); - expect(res.reaction.type, reactionType); - expect(res.reaction.messageId, messageId); + final res = await client.sendReaction(messageId, reactionType); + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.type, reactionType); + expect(res.reaction.messageId, messageId); - verify(() => api.message.sendReaction(messageId, reactionType)).called(1); - verifyNoMoreInteractions(api.message); + verify(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.sendReaction with score`', () async { + const messageId = 'test-message-id'; + const reactionType = 'like'; + const score = 3; + const extraData = {'score': score}; + + when(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).thenAnswer((_) async => SendReactionResponse() + ..message = Message(id: messageId) + ..reaction = Reaction( + type: reactionType, + messageId: messageId, + score: score, + )); + + final res = await client.sendReaction( + messageId, + reactionType, + score: score, + ); + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.type, reactionType); + expect(res.reaction.messageId, messageId); + expect(res.reaction.score, score); + + verify(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).called(1); + verifyNoMoreInteractions(api.message); + }); + + test('`.sendReaction with score passed in extradata also`', () async { + const messageId = 'test-message-id'; + const reactionType = 'like'; + const score = 3; + const extraDataScore = 5; + const extraData = {'score': extraDataScore}; + + when(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).thenAnswer((_) async => SendReactionResponse() + ..message = Message(id: messageId) + ..reaction = Reaction( + type: reactionType, + messageId: messageId, + score: extraDataScore, + )); + + final res = await client.sendReaction( + messageId, + reactionType, + score: score, + extraData: extraData, + ); + expect(res, isNotNull); + expect(res.message.id, messageId); + expect(res.reaction.type, reactionType); + expect(res.reaction.messageId, messageId); + expect(res.reaction.score, extraDataScore); + + verify(() => api.message.sendReaction( + messageId, + reactionType, + extraData: extraData, + )).called(1); + verifyNoMoreInteractions(api.message); + }); }); test('`.deleteReaction`', () async { From 392a5ffa531bb568f249881e28e006cf84bc9bee Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 19:13:34 +0530 Subject: [PATCH 11/35] Made an entry into stream_chat changelog --- packages/stream_chat/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 787aa2af..ece8a30e 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,7 @@ +## Upcoming + +- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions + ## 3.4.0 🐞 Fixed From 0db466b5b3614f67d48890d4aab4e612bbaa377a Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 19:21:20 +0530 Subject: [PATCH 12/35] Changelog addition --- packages/stream_chat/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 8226e977..ee554704 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,5 +1,7 @@ ## Upcoming +✅ Added + - You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions 🐞 Fixed From 6a4caff89783aeec1ec9d80ea1843c66b4e1a377 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 16 Feb 2022 19:25:50 +0530 Subject: [PATCH 13/35] Removed extra - from changelog --- packages/stream_chat/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index ee554704..91077303 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -7,7 +7,6 @@ 🐞 Fixed - [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890). Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). -- ## 3.4.0 🐞 Fixed From 8f6bfd7b472acfbe007c4501b87c3b3584680fb7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 17 Feb 2022 17:17:52 +0530 Subject: [PATCH 14/35] fix(llc): improve removeMessage logic. --- .../stream_chat/lib/src/client/channel.dart | 116 ++++++++---------- .../lib/src/client/retry_queue.dart | 2 +- 2 files changed, 51 insertions(+), 67 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 84f0321e..b5bad6cb 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -407,7 +407,7 @@ class Channel { if (index != -1) { final newAttachments = [...message!.attachments]..[index] = attachment; final updatedMessage = message!.copyWith(attachments: newAttachments); - state?.addMessage(updatedMessage); + state?.updateMessage(updatedMessage); // updating original message for next iteration message = message!.merge(updatedMessage); } @@ -512,7 +512,7 @@ class Channel { ).toList(), ); - state!.addMessage(message); + state!.updateMessage(message); try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { @@ -535,7 +535,7 @@ class Channel { type, skipPush: skipPush, ); - state!.addMessage(response.message); + state!.updateMessage(response.message); if (cooldown > 0) cooldownStartedAt = DateTime.now(); return response; } catch (e) { @@ -571,7 +571,7 @@ class Channel { ).toList(), ); - state?.addMessage(message); + state?.updateMessage(message); try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { @@ -594,7 +594,7 @@ class Channel { ownReactions: message.ownReactions, ); - state?.addMessage(m); + state?.updateMessage(m); return response; } catch (e) { @@ -602,7 +602,7 @@ class Channel { if (e.isRetriable) { state!._retryQueue.add([message]); } else { - state?.addMessage(originalMessage); + state?.updateMessage(originalMessage); } } rethrow; @@ -630,7 +630,7 @@ class Channel { ownReactions: message.ownReactions, ); - state?.addMessage(updatedMessage); + state?.updateMessage(updatedMessage); return response; } catch (e) { @@ -646,7 +646,7 @@ class Channel { // Directly deleting the local messages which are not yet sent to server if (message.status == MessageSendingStatus.sending || message.status == MessageSendingStatus.failed) { - state!.addMessage(message.copyWith( + state!.updateMessage(message.copyWith( type: 'deleted', status: MessageSendingStatus.sent, )); @@ -667,11 +667,11 @@ class Channel { deletedAt: message.deletedAt ?? DateTime.now(), ); - state?.addMessage(message); + state?.updateMessage(message); final response = await _client.deleteMessage(message.id, hard: hard); - state?.addMessage(message.copyWith(status: MessageSendingStatus.sent)); + state?.updateMessage(message.copyWith(status: MessageSendingStatus.sent)); return response; } catch (e) { @@ -860,7 +860,7 @@ class Channel { ownReactions: ownReactions, ); - state?.addMessage(newMessage); + state?.updateMessage(newMessage); try { final reactionResp = await _client.sendReaction( @@ -872,7 +872,7 @@ class Channel { return reactionResp; } catch (_) { // Reset the message if the update fails - state?.addMessage(message); + state?.updateMessage(message); rethrow; } } @@ -912,7 +912,7 @@ class Channel { ownReactions: ownReactions, ); - state?.addMessage(newMessage); + state?.updateMessage(newMessage); try { final deleteResponse = await _client.deleteReaction( @@ -922,7 +922,7 @@ class Channel { return deleteResponse; } catch (_) { // Reset the message if the update fails - state?.addMessage(message); + state?.updateMessage(message); rethrow; } } @@ -1079,11 +1079,11 @@ class Channel { // update the passed message with response message if (res.message != null) { - state!.addMessage(res.message!); + state!.updateMessage(res.message!); } else { // remove the passed message if response does // not contain message - state!.removeMessage(message, decreaseReplyCount: true); + state!.removeMessage(message); await _client.chatPersistenceClient?.deleteMessageById(messageId); } return res; @@ -1322,7 +1322,8 @@ class Channel { /// Remove the ban for the user with given [userID] in the channel. @Deprecated( - "Use 'unbanMember' instead. This method will be removed in v4.0.0") + "Use 'unbanMember' instead. This method will be removed in v4.0.0", + ) Future unbanUser(String userID) => unbanMember(userID); /// Remove the ban for the member with given [userID] in the channel. @@ -1694,7 +1695,9 @@ class ChannelClientState { void _listenReactionDeleted() { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final reaction = event.reaction; final ownReactions = oldMessage?.ownReactions ?.whereNot((it) => @@ -1707,18 +1710,20 @@ class ChannelClientState { final message = event.message!.copyWith( ownReactions: ownReactions, ); - addMessage(message); + updateMessage(message); })); } void _listenReactions() { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); - addMessage(message); + updateMessage(message); })); } @@ -1730,12 +1735,13 @@ class ChannelClientState { ) .listen((event) { final oldMessage = - messages.firstWhereOrNull((it) => it.id == event.message?.id); - + messages.firstWhereOrNull((it) => it.id == event.message?.id) ?? + threads[event.message?.parentId] + ?.firstWhereOrNull((e) => e.id == event.message?.id); final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); - addMessage(message); + updateMessage(message); if (message.pinned) { _channelState = _channelState.copyWith( @@ -1752,10 +1758,9 @@ class ChannelClientState { _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { final message = event.message!; if (event.hardDelete == true) { - //do not decrease reply count here - it is done in _listenMessageUpdated - removeMessage(message, hardDelete: true); + removeMessage(message); } else { - addMessage(message); + updateMessage(message); } })); } @@ -1770,7 +1775,7 @@ class ChannelClientState { final message = event.message!; if (isUpToDate || (message.parentId != null && message.showInChannel != true)) { - addMessage(message); + updateMessage(message); } if (_countMessageAsUnread(message)) { @@ -1780,9 +1785,13 @@ class ChannelClientState { } /// Add a [message] to this [channelState]. - void addMessage(Message message) { + @Deprecated('Use updateMessage instead') + void addMessage(Message message) => updateMessage(message); + + /// Updates the [message] in the state if it exists. Adds it otherwise. + void updateMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { - final newMessages = List.from(_channelState.messages); + final newMessages = [...messages]; final oldIndex = newMessages.indexWhere((m) => m.id == message.id); if (oldIndex != -1) { Message? m; @@ -1811,47 +1820,22 @@ class ChannelClientState { } /// Remove a [message] from this [channelState]. - void removeMessage( - Message message, { - bool hardDelete = false, - bool decreaseReplyCount = false, - }) { + void removeMessage(Message message) { final parentId = message.parentId; - // i.e. it's a thread message - // 1. Remove the thread message - // 2. Reduce total reply count of parent message + // i.e. it's a thread message, Remove it if (parentId != null) { - if (decreaseReplyCount) { - final allMessages = [...messages]; - final parentMessage = allMessages.firstWhereOrNull( - (it) => it.id == parentId, - ); - - // return if message not available in the memory - if (parentMessage == null) return; - final replyCount = parentMessage.replyCount; - // return if reply count is null or zero - if (replyCount == null || replyCount == 0) return; - - addMessage(parentMessage.copyWith(replyCount: replyCount - 1)); - } - updateThreadInfo( + final threadMessages = [...threads[parentId]!]; + return updateThreadInfo( parentId, - threads[parentId]! - ..removeWhere( - (e) => e.id == message.id, - ), + threadMessages..removeWhere((e) => e.id == message.id), ); - } else { - // Remove regular message - final allMessages = [...messages]; - if (hardDelete) { - allMessages.removeWhere((e) => e.id == message.id); - _channelState = _channelState.copyWith(messages: allMessages); - } else if (allMessages.remove(message)) { - _channelState = _channelState.copyWith(messages: allMessages); - } } + + // Remove regular message + final allMessages = [...messages]; + _channelState = _channelState.copyWith( + messages: allMessages..removeWhere((e) => e.id == message.id), + ); } void _listenReadEvents() { diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index 17b019a5..5dfa9aa3 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -158,7 +158,7 @@ class RetryQueue { : message.status == MessageSendingStatus.updating ? MessageSendingStatus.failed_update : MessageSendingStatus.failed_delete; - channel.state?.addMessage(message.copyWith(status: newStatus)); + channel.state?.updateMessage(message.copyWith(status: newStatus)); } Future _retryMessage(Message message) async { From ca01ca28d5a1c261f149d10847e6f70324550338 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Feb 2022 14:58:18 +0530 Subject: [PATCH 15/35] fix(llc): add type check in auth_interceptor.dart Signed-off-by: xsahil03x --- .../lib/src/core/http/interceptor/auth_interceptor.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 37f618f5..0ef1a408 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -49,10 +49,13 @@ class AuthInterceptor extends Interceptor { DioError err, ErrorInterceptorHandler handler, ) async { - ErrorResponse? error; final data = err.response?.data; - if (data != null) error = ErrorResponse.fromJson(data); - if (error?.code == ChatErrorCode.tokenExpired.code) { + if (data == null || data is! Map) { + return handler.next(err); + } + + final error = ErrorResponse.fromJson(data); + if (error.code == ChatErrorCode.tokenExpired.code) { if (_tokenManager.isStatic) return handler.next(err); _client.lock(); await _tokenManager.loadToken(refresh: true); From d21adb99571a789d3127a25e4d34004c16226041 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Feb 2022 15:00:03 +0530 Subject: [PATCH 16/35] chore(llc): Update CHANGELOG.md Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 91077303..0c35a7a3 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -6,16 +6,21 @@ 🐞 Fixed -- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890). Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). +- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. + Thanks [bstolinski](https://github.com/bstolinski). +- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. + ## 3.4.0 🐞 Fixed - [[#857]](https://github.com/GetStream/stream-chat-flutter/issues/857) Channel now listens for member ban/unban and updates the channel state with the latest data. -- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` is now also included while saving users in persistence. +- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` is now also included while saving + users in persistence. - [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion. -- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated when receiving a reaction event. +- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated + when receiving a reaction event. - Add check for invalid image URLs - Fix `channelState.pinnedMessagesStream` getting reset to `0` after a channel update. - Fixed `unreadCount` after removing user from a channel. From ea38750a44682521a764377eff9d51e82919a1a8 Mon Sep 17 00:00:00 2001 From: S-ecki <75510543+S-ecki@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:39:17 +0100 Subject: [PATCH 17/35] update chewie to newest version fixes issue #805 --- packages/stream_chat_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index eba6b7f8..348b8205 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -12,7 +12,7 @@ environment: dependencies: cached_network_image: ^3.0.0 characters: ^1.1.0 - chewie: ^1.2.0 + chewie: ^1.3.0 collection: ^1.15.0 diacritic: ^0.1.3 dio: ^4.0.0 From d4b9a10e54c5faa3132a81900ea4e11ba731c74e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Feb 2022 17:12:27 +0530 Subject: [PATCH 18/35] feat(llc): add support for `partialUserUpdate` endpoint. Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/client.dart | 22 ++++++++++ .../lib/src/core/api/requests.dart | 26 ++++++++++++ .../lib/src/core/api/requests.g.dart | 11 +++++ .../lib/src/core/api/user_api.dart | 13 ++++++ .../lib/src/core/models/channel_state.g.dart | 2 +- .../test/src/client/client_test.dart | 40 +++++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9c593694..c3605ac5 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1073,6 +1073,28 @@ class StreamChatClient { Future updateUsers(List users) => _chatApi.user.updateUsers(users); + /// Partially update the given user with [id]. + /// Use [set] to define values to be set. + /// Use [unset] to define values to be unset. + Future partialUpdateUser( + String id, { + Map? set, + List? unset, + }) { + final user = PartialUpdateUserRequest( + id: id, + set: set, + unset: unset, + ); + return partialUpdateUsers([user]); + } + + /// Batch partial updates the [users]. + Future partialUpdateUsers( + List users, + ) => + _chatApi.user.partialUpdateUsers(users); + /// Bans a user from all channels Future banUser( String targetUserId, [ diff --git a/packages/stream_chat/lib/src/core/api/requests.dart b/packages/stream_chat/lib/src/core/api/requests.dart index 6f00b374..3f4e79de 100644 --- a/packages/stream_chat/lib/src/core/api/requests.dart +++ b/packages/stream_chat/lib/src/core/api/requests.dart @@ -156,3 +156,29 @@ class PaginationParams extends Equatable { lessThanOrEqual, ]; } + +/// Request model for the [client.partialUpdateUser] api call. +@JsonSerializable(createFactory: false) +class PartialUpdateUserRequest extends Equatable { + /// Creates a new PartialUpdateUserRequest instance. + const PartialUpdateUserRequest({ + required this.id, + this.set, + this.unset, + }); + + /// User ID. + final String id; + + /// Fields to set. + final Map? set; + + /// Fields to unset. + final List? unset; + + /// Serialize model to json + Map toJson() => _$PartialUpdateUserRequestToJson(this); + + @override + List get props => [id, set, unset]; +} diff --git a/packages/stream_chat/lib/src/core/api/requests.g.dart b/packages/stream_chat/lib/src/core/api/requests.g.dart index 7d45ee86..62ec12b3 100644 --- a/packages/stream_chat/lib/src/core/api/requests.g.dart +++ b/packages/stream_chat/lib/src/core/api/requests.g.dart @@ -54,3 +54,14 @@ Map _$PaginationParamsToJson(PaginationParams instance) { writeNotNull('id_lte', instance.lessThanOrEqual); return val; } + +Map _$PartialUpdateUserRequestToJson( + PartialUpdateUserRequest instance) => + { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'id': instance.id, + 'set': instance.set, + 'unset': instance.unset, + 'props': instance.props, + }; diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart index 61159731..916976c8 100644 --- a/packages/stream_chat/lib/src/core/api/user_api.dart +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -46,4 +46,17 @@ class UserApi { ); return UpdateUsersResponse.fromJson(response.data); } + + /// Batch partial update of [users]. + Future partialUpdateUsers( + List users, + ) async { + final response = await _client.patch( + '/users', + data: { + 'users': users, + }, + ); + return UpdateUsersResponse.fromJson(response.data); + } } diff --git a/packages/stream_chat/lib/src/core/models/channel_state.g.dart b/packages/stream_chat/lib/src/core/models/channel_state.g.dart index 0da51768..5100932c 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.g.dart @@ -21,7 +21,7 @@ ChannelState _$ChannelStateFromJson(Map json) => ChannelState( pinnedMessages: (json['pinned_messages'] as List?) ?.map((e) => Message.fromJson(e as Map)) .toList() ?? - const [], + _emptyPinnedMessages, watcherCount: json['watcher_count'] as int?, watchers: (json['watchers'] as List?) ?.map((e) => User.fromJson(e as Map)) diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index c9aa8968..b59937bb 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -1772,6 +1772,46 @@ void main() { verifyNoMoreInteractions(api.user); }); + test('`.partialUpdateUser`', () async { + const userId = 'test-user-id'; + + final set = {'color': 'yellow'}; + final unset = []; + + final partialUpdateRequest = PartialUpdateUserRequest( + id: userId, + set: set, + unset: unset, + ); + + final updatedUser = User( + id: userId, + extraData: {'color': set['color']}, + ); + + when(() => api.user.partialUpdateUsers([partialUpdateRequest])) + .thenAnswer( + (_) async => UpdateUsersResponse() + ..users = { + updatedUser.id: updatedUser, + }, + ); + + final res = await client.partialUpdateUser( + userId, + set: set, + unset: unset, + ); + + expect(res, isNotNull); + expect(res.users, {updatedUser.id: updatedUser}); + + verify( + () => api.user.partialUpdateUsers([partialUpdateRequest]), + ).called(1); + verifyNoMoreInteractions(api.user); + }); + test('`.banUser`', () async { const userId = 'test-user-id'; From 3886f10387e436bd1a8775f3b10cfe5e49de6a78 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Feb 2022 17:13:36 +0530 Subject: [PATCH 19/35] chore(llc): update CHANGELOG.md Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 0c35a7a3..82225b27 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -2,7 +2,8 @@ ✅ Added -- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions +- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions. +- Added new `client.partialUpdateUsers` function in order to partially update users. 🐞 Fixed From adf7befd6894392356f1ba9dcd90e495849d2aed Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 18 Feb 2022 17:54:49 +0530 Subject: [PATCH 20/35] test(llc): add `userApi.partialUpdateUsers` test. Signed-off-by: xsahil03x --- .../test/src/core/api/user_api_test.dart | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart index dbf83f2c..764386f8 100644 --- a/packages/stream_chat/test/src/core/api/user_api_test.dart +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -82,4 +82,37 @@ void main() { verify(() => client.post(path, data: any(named: 'data'))).called(1); verifyNoMoreInteractions(client); }); + + test('partialUpdateUsers', () async { + const user = PartialUpdateUserRequest( + id: 'test-user-id', + set: {'color': 'yellow'}, + ); + + const path = '/users'; + + final updatedUser = {user.id: User(id: user.id, extraData: user.set!)}; + + when(() => client.patch(path, data: { + 'users': [user], + })).thenAnswer( + (_) async => successResponse( + path, + data: { + 'users': + updatedUser.map((key, value) => MapEntry(key, value.toJson())) + }, + ), + ); + + final res = await userApi.partialUpdateUsers([user]); + + expect(res, isNotNull); + expect(res.users.length, updatedUser.length); + + verify(() => client.patch(path, data: { + 'users': [user] + })).called(1); + verifyNoMoreInteractions(client); + }); } From cfb41dd62943253074bedad47c95f91fc3cfa4aa Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Fri, 18 Feb 2022 15:26:44 +0100 Subject: [PATCH 21/35] fix: scrollToBottom not respecting false value --- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../lib/src/message_list_view.dart | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d68549e1..116d1624 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ 🐞 Fixed - [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`. +- Fix `showScrollToBottom` in `MessageListView` not respecting false value. ## 3.4.0 - Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). 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 edd9c601..c6e269b9 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -712,20 +712,21 @@ class _MessageListViewState extends State { ); }, ), - BetterStreamBuilder( - stream: streamChannel!.channel.state!.isUpToDateStream, - initialData: streamChannel!.channel.state!.isUpToDate, - builder: (context, snapshot) => ValueListenableBuilder( - valueListenable: _showScrollToBottom, - child: _buildScrollToBottom(), - builder: (context, value, child) { - if (!snapshot || value) { - return child!; - } - return const Offstage(); - }, + if (widget.showScrollToBottom) + BetterStreamBuilder( + stream: streamChannel!.channel.state!.isUpToDateStream, + initialData: streamChannel!.channel.state!.isUpToDate, + builder: (context, snapshot) => ValueListenableBuilder( + valueListenable: _showScrollToBottom, + child: _buildScrollToBottom(), + builder: (context, value, child) { + if (!snapshot || value) { + return child!; + } + return const Offstage(); + }, + ), ), - ), if (widget.showFloatingDateDivider) _buildFloatingDateDivider(itemCount), ], From 27cb7d2f9806136158491966ae8315d744f9c9bb Mon Sep 17 00:00:00 2001 From: S-ecki <75510543+S-ecki@users.noreply.github.com> Date: Fri, 18 Feb 2022 17:08:32 +0100 Subject: [PATCH 22/35] Update CHANGELOG.md --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d68549e1..48aaacfc 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ 🐞 Fixed - [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`. +- [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0 ## 3.4.0 - Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). From d2a0fd603dd5339fd1049a7be942007f594ee1e5 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Mon, 21 Feb 2022 11:00:54 +0100 Subject: [PATCH 23/35] fix: controllers not disposed --- packages/stream_chat_flutter/lib/src/full_screen_media.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index 83ae96b1..de4ee001 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -264,9 +264,11 @@ class _FullScreenMediaState extends State ); @override - void dispose() async { + void dispose() { + _controller.dispose(); + _pageController.dispose(); for (final package in videoPackages.values) { - await package.dispose(); + package.dispose(); } super.dispose(); } From ae44406b96ec39a9cc275c8d51ddf841bf7df9eb Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 21 Feb 2022 15:31:23 +0530 Subject: [PATCH 24/35] fix(llc): Fix `channelState.copyWith` with respect to pinnedMessages. Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 1 + .../stream_chat/lib/src/core/models/channel_state.dart | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 82225b27..ef66f835 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -10,6 +10,7 @@ - [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). - [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. +- Fix `channelState.copyWith` with respect to pinnedMessages. ## 3.4.0 diff --git a/packages/stream_chat/lib/src/core/models/channel_state.dart b/packages/stream_chat/lib/src/core/models/channel_state.dart index 3ddc2b68..8b500d18 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.dart @@ -67,9 +67,10 @@ class ChannelState { members: members ?? this.members, // Hack to avoid using the default value in case nothing is provided. // FIXME: Use non-nullable by default instead of empty list. - pinnedMessages: pinnedMessages == _emptyPinnedMessages - ? this.pinnedMessages - : pinnedMessages ?? _emptyPinnedMessages, + pinnedMessages: + pinnedMessages == null || pinnedMessages == _emptyPinnedMessages + ? this.pinnedMessages + : pinnedMessages, watcherCount: watcherCount ?? this.watcherCount, watchers: watchers ?? this.watchers, read: read ?? this.read, From 2cef5396397b828404e5fc8d78b2266b953232d6 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Mon, 21 Feb 2022 12:19:36 +0100 Subject: [PATCH 25/35] perf: improve full screen media animations --- .../lib/src/full_screen_media.dart | 232 +++++++++--------- 1 file changed, 117 insertions(+), 115 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index de4ee001..aa185597 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -65,24 +65,35 @@ class FullScreenMedia extends StatefulWidget { class _FullScreenMediaState extends State with SingleTickerProviderStateMixin { - bool _optionsShown = true; - - late final AnimationController _controller; + late final AnimationController _animationController; late final PageController _pageController; - late int _currentPage; + late final _curvedAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeOut, + reverseCurve: Curves.easeIn, + ); + + final _opacityTween = Tween(begin: 1, end: 0); + late final _opacityAnimation = _opacityTween.animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0, 0.6, curve: Curves.easeOut), + ), + ); + + late final ValueNotifier _currentPage = ValueNotifier(widget.startIndex); final videoPackages = {}; @override void initState() { super.initState(); - _controller = AnimationController( + _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _pageController = PageController(initialPage: widget.startIndex); - _currentPage = widget.startIndex; for (var i = 0; i < widget.mediaAttachments.length; i++) { final attachment = widget.mediaAttachments[i]; if (attachment.type != 'video') continue; @@ -116,41 +127,38 @@ class _FullScreenMediaState extends State resizeToAvoidBottomInset: false, body: Stack( children: [ - AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) => PageView.builder( - controller: _pageController, - onPageChanged: (val) { - setState(() { - _currentPage = val; - }); + PageView.builder( + controller: _pageController, + onPageChanged: (val) { + _currentPage.value = val; - if (videoPackages.isEmpty) { - return; + if (videoPackages.isEmpty) { + return; + } + + final currentAttachment = widget.mediaAttachments[val]; + + for (final e in videoPackages.values) { + if (e._attachment != currentAttachment) { + e._chewieController?.pause(); } + } - final currentAttachment = widget.mediaAttachments[val]; - - for (final e in videoPackages.values) { - if (e._attachment != currentAttachment) { - e._chewieController?.pause(); - } - } - - if (widget.autoplayVideos && - currentAttachment.type == 'video') { - final controller = videoPackages[currentAttachment.id]!; - controller._chewieController?.play(); - } - }, - itemBuilder: (context, index) { - final attachment = widget.mediaAttachments[index]; - if (attachment.type == 'image' || - attachment.type == 'giphy') { - final imageUrl = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl; - return PhotoView( + if (widget.autoplayVideos && + currentAttachment.type == 'video') { + final controller = videoPackages[currentAttachment.id]!; + controller._chewieController?.play(); + } + }, + itemBuilder: (context, index) { + final attachment = widget.mediaAttachments[index]; + if (attachment.type == 'image' || attachment.type == 'giphy') { + final imageUrl = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + return AnimatedBuilder( + animation: _curvedAnimation, + builder: (context, child) => PhotoView( loadingBuilder: (context, image) => const Offstage(), imageProvider: (imageUrl == null && attachment.localUri != null && @@ -166,97 +174,91 @@ class _FullScreenMediaState extends State color: ColorTween( begin: ChannelHeaderTheme.of(context).color, end: Colors.black, - ).lerp(_controller.value), + ).lerp(_curvedAnimation.value), ), onTapUp: (a, b, c) { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); + if (_animationController.isCompleted) { + _animationController.reverse(); } else { - _controller.forward(); + _animationController.forward(); } }, - ); - } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]!; - if (!controller.initialized) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return InkWell( - onTap: () { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 50, - ), - child: Chewie( - controller: controller.chewieController!, - ), - ), + ), + ); + } else if (attachment.type == 'video') { + final controller = videoPackages[attachment.id]!; + if (!controller.initialized) { + return const Center( + child: CircularProgressIndicator(), ); } - return Container(); - }, - itemCount: widget.mediaAttachments.length, - ), - ), - AnimatedOpacity( - opacity: _optionsShown ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GalleryHeader( - userName: widget.userName, - sentAt: context.translations.sentAtText( - date: widget.message.createdAt, - time: widget.message.createdAt, + return InkWell( + onTap: () { + if (_animationController.isCompleted) { + _animationController.reverse(); + } else { + _animationController.forward(); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 50, + ), + child: Chewie( + controller: controller.chewieController!, + ), ), - onBackPressed: () { - Navigator.of(context).pop(); - }, - message: widget.message, - currentIndex: _currentPage, - onShowMessage: () { - widget.onShowMessage?.call( - widget.message, - StreamChannel.of(context).channel, - ); - }, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, - ), - if (!widget.message.isEphemeral) - GalleryFooter( - currentPage: _currentPage, - totalPages: widget.mediaAttachments.length, - mediaAttachments: widget.mediaAttachments, + ); + } + return const SizedBox(); + }, + itemCount: widget.mediaAttachments.length, + ), + FadeTransition( + opacity: _opacityAnimation, + child: ValueListenableBuilder( + valueListenable: _currentPage, + builder: (context, value, child) => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GalleryHeader( + userName: widget.userName, + sentAt: context.translations.sentAtText( + date: widget.message.createdAt, + time: widget.message.createdAt, + ), + onBackPressed: () { + Navigator.of(context).pop(); + }, message: widget.message, - mediaSelectedCallBack: (val) { - setState(() { - _currentPage = val; + currentIndex: value, + onShowMessage: () { + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); + }, + attachmentActionsModalBuilder: + widget.attachmentActionsModalBuilder, + ), + if (!widget.message.isEphemeral) + GalleryFooter( + currentPage: value, + totalPages: widget.mediaAttachments.length, + mediaAttachments: widget.mediaAttachments, + message: widget.message, + mediaSelectedCallBack: (val) { + _currentPage.value = val; _pageController.animateToPage( val, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); Navigator.pop(context); - }); - }, - ), - ], + }, + ), + ], + ), ), ), ], @@ -265,7 +267,7 @@ class _FullScreenMediaState extends State @override void dispose() { - _controller.dispose(); + _animationController.dispose(); _pageController.dispose(); for (final package in videoPackages.values) { package.dispose(); From e9626dc348ae9ecefe8df29b3667bf95de69de57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Stolin=CC=81ski?= Date: Tue, 22 Feb 2022 09:42:21 +0100 Subject: [PATCH 26/35] fix removing message from thread --- packages/stream_chat/CHANGELOG.md | 2 ++ .../stream_chat/lib/src/client/channel.dart | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 82225b27..59feb777 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -10,6 +10,8 @@ - [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). - [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. +- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891). Fixed reply counter for parent message not + updating correctly after deleting thread message. ## 3.4.0 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index c27f3634..2b311061 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1826,11 +1826,19 @@ class ChannelClientState { final parentId = message.parentId; // i.e. it's a thread message, Remove it if (parentId != null) { - final threadMessages = [...threads[parentId]!]; - return updateThreadInfo( - parentId, - threadMessages..removeWhere((e) => e.id == message.id), - ); + if (!threads.containsKey(parentId)) { + return; + } + + final newThreads = Map>.from(threads); + + newThreads[parentId] = [ + ...newThreads[parentId]! + ..removeWhere((e) => e.id == message.id), + ]; + + _threads = newThreads; + return; } // Remove regular message From 52b035cb5fc226c63771bac75b44760116e11b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Stolin=CC=81ski?= Date: Tue, 22 Feb 2022 09:49:18 +0100 Subject: [PATCH 27/35] fix formatting --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/src/client/channel.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 59feb777..6bbc3b16 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -10,7 +10,7 @@ - [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages. Thanks [bstolinski](https://github.com/bstolinski). - [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`. -- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891). Fixed reply counter for parent message not +- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891) Fixed reply counter for parent message not updating correctly after deleting thread message. ## 3.4.0 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 2b311061..072bb150 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1833,8 +1833,7 @@ class ChannelClientState { final newThreads = Map>.from(threads); newThreads[parentId] = [ - ...newThreads[parentId]! - ..removeWhere((e) => e.id == message.id), + ...newThreads[parentId]!..removeWhere((e) => e.id == message.id), ]; _threads = newThreads; From a361e89abad9cc5e70980cf86067518a3392ce3f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Feb 2022 17:55:34 +0530 Subject: [PATCH 28/35] fix(llc): fix `pinnedMessage` inconsistencies. Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 38 +++++++++++-------- .../lib/src/core/models/channel_state.dart | 9 ++--- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 76fbab7a..dd6fcff1 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1744,15 +1744,6 @@ class ChannelClientState { ownReactions: oldMessage?.ownReactions, ); addMessage(message); - - if (message.pinned) { - _channelState = _channelState.copyWith( - pinnedMessages: [ - ..._channelState.pinnedMessages, - message, - ], - ); - } })); } @@ -1804,8 +1795,24 @@ class ChannelClientState { newMessages.add(message); } + final newPinnedMessages = [...pinnedMessages]; + final oldPinnedIndex = + newPinnedMessages.indexWhere((m) => m.id == message.id); + + // Handle pinned messages + if (message.pinned) { + if (oldPinnedIndex != -1) { + newPinnedMessages[oldPinnedIndex] = message; + } else { + newPinnedMessages.add(message); + } + } else { + newPinnedMessages.removeWhere((m) => m.id == message.id); + } + _channelState = _channelState.copyWith( messages: newMessages..sort(_sortByCreatedAt), + pinnedMessages: newPinnedMessages, channel: _channelState.channel?.copyWith( lastMessageAt: message.createdAt, ), @@ -1898,11 +1905,12 @@ class ChannelClientState { .distinct(const ListEquality().equals); /// Channel pinned message list. - List get pinnedMessages => _channelState.pinnedMessages.toList(); + List get pinnedMessages => _channelState.pinnedMessages; /// Channel pinned message list as a stream. - Stream> get pinnedMessagesStream => - channelStateStream.map((cs) => cs.pinnedMessages.toList()); + Stream> get pinnedMessagesStream => channelStateStream + .map((cs) => cs.pinnedMessages) + .distinct(const ListEquality().equals); /// Get channel last message. Message? get lastMessage => @@ -2213,7 +2221,7 @@ class ChannelClientState { .toList(); updateChannelState(_channelState.copyWith( - pinnedMessages: pinnedMessages.where(_pinIsValid()).toList(), + pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), messages: expiredMessages, )); } @@ -2250,7 +2258,7 @@ class ChannelClientState { } } -bool Function(Message) _pinIsValid() { +bool _pinIsValid(Message message) { final now = DateTime.now(); - return (Message m) => m.pinExpires!.isAfter(now); + return message.pinExpires!.isAfter(now); } diff --git a/packages/stream_chat/lib/src/core/models/channel_state.dart b/packages/stream_chat/lib/src/core/models/channel_state.dart index 8b500d18..17439dc0 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.dart @@ -56,7 +56,7 @@ class ChannelState { ChannelModel? channel, List? messages, List? members, - List? pinnedMessages, + List pinnedMessages = _emptyPinnedMessages, int? watcherCount, List? watchers, List? read, @@ -67,10 +67,9 @@ class ChannelState { members: members ?? this.members, // Hack to avoid using the default value in case nothing is provided. // FIXME: Use non-nullable by default instead of empty list. - pinnedMessages: - pinnedMessages == null || pinnedMessages == _emptyPinnedMessages - ? this.pinnedMessages - : pinnedMessages, + pinnedMessages: pinnedMessages == _emptyPinnedMessages + ? this.pinnedMessages + : pinnedMessages, watcherCount: watcherCount ?? this.watcherCount, watchers: watchers ?? this.watchers, read: read ?? this.read, From c7ba48a446902ccfb0debb615add836f056dd90c Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Thu, 24 Feb 2022 15:56:19 +0530 Subject: [PATCH 29/35] Pass null for onImageTap when no function is passed --- packages/stream_chat_flutter/lib/src/channel_list_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 0ceff00e..bc4f4b19 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -599,7 +599,9 @@ class _ChannelListViewState extends State { child: ChannelPreview( onLongPress: widget.onChannelLongPress, channel: channel, - onImageTap: () => widget.onImageTap?.call(channel), + onImageTap: widget.onImageTap != null + ? () => widget.onImageTap!(channel) + : null, onTap: (channel) => onTap(channel, widget.channelWidget), ), ), From e7d8810722414fd0726796e1c36e605f42eb44b8 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Thu, 24 Feb 2022 16:05:00 +0530 Subject: [PATCH 30/35] Added entry in changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 4bc77514..d4a64c1a 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -5,6 +5,7 @@ - [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`. - [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0 - Fix `showScrollToBottom` in `MessageListView` not respecting false value. +- Fix default `Channel` route not opening from `ChannelListView` when `ChannelAvatar` is tapped ## 3.4.0 - Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). From 8bfd93266eda2df7b9f93c8491929666b5361fc7 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Thu, 24 Feb 2022 16:30:51 +0530 Subject: [PATCH 31/35] Fix: variable for channel was named client --- packages/stream_chat_flutter/lib/src/channel_list_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index bc4f4b19..648226ce 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -614,7 +614,7 @@ class _ChannelListViewState extends State { if (widget.onChannelTap != null) { onTap = widget.onChannelTap!; } else { - onTap = (client, _) { + onTap = (channel, _) { if (widget.channelWidget == null) { return; } @@ -622,7 +622,7 @@ class _ChannelListViewState extends State { context, MaterialPageRoute( builder: (context) => StreamChannel( - channel: client, + channel: channel, child: widget.channelWidget!, ), ), From 655da0ba33855a667108391fdd89a8f6d2ccfd6e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 1 Mar 2022 14:19:20 +0530 Subject: [PATCH 32/35] fix(llc): Remove regular message in case `showInChannel` is true. (#3) * fix(llc): also remove regular message in case `showInChannel` is true. * fix(llc): use `hard` param while removing message in channel state. --- .../stream_chat/lib/src/client/channel.dart | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 072bb150..b0f1bdd0 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -643,13 +643,18 @@ class Channel { /// Deletes the [message] from the channel. Future deleteMessage(Message message, {bool? hard}) async { + final hardDelete = hard ?? false; + // Directly deleting the local messages which are not yet sent to server if (message.status == MessageSendingStatus.sending || message.status == MessageSendingStatus.failed) { - state!.updateMessage(message.copyWith( - type: 'deleted', - status: MessageSendingStatus.sent, - )); + state!.deleteMessage( + message.copyWith( + type: 'deleted', + status: MessageSendingStatus.sent, + ), + hardDelete: hardDelete, + ); // Removing the attachments upload completer to stop the `sendMessage` // waiting for attachments to complete. @@ -667,11 +672,14 @@ class Channel { deletedAt: message.deletedAt ?? DateTime.now(), ); - state?.updateMessage(message); + state?.deleteMessage(message, hardDelete: hardDelete); final response = await _client.deleteMessage(message.id, hard: hard); - state?.updateMessage(message.copyWith(status: MessageSendingStatus.sent)); + state?.deleteMessage( + message.copyWith(status: MessageSendingStatus.sent), + hardDelete: hardDelete, + ); return response; } catch (e) { @@ -1826,27 +1834,33 @@ class ChannelClientState { final parentId = message.parentId; // i.e. it's a thread message, Remove it if (parentId != null) { - if (!threads.containsKey(parentId)) { - return; - } + final newThreads = {...threads}; + // Early return in case the thread is not available + if (!newThreads.containsKey(parentId)) return; - final newThreads = Map>.from(threads); + _threads = newThreads + ..update( + parentId, + (messages) => messages..removeWhere((e) => e.id == message.id), + ); - newThreads[parentId] = [ - ...newThreads[parentId]!..removeWhere((e) => e.id == message.id), - ]; - - _threads = newThreads; - return; + // Early return if the thread message is not shown in channel. + if (message.showInChannel == false) return; } - // Remove regular message + // Remove regular message, thread message shown in channel final allMessages = [...messages]; _channelState = _channelState.copyWith( messages: allMessages..removeWhere((e) => e.id == message.id), ); } + /// Removes/Updates the [message] based on the [hardDelete] value. + void deleteMessage(Message message, {bool hardDelete = false}) { + if (hardDelete) return removeMessage(message); + return updateMessage(message); + } + void _listenReadEvents() { if (_channelState.channel?.config.readEvents == false) { return; From 98dbe30ffbf77f75d98be6b854d8c1e4aa454790 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 1 Mar 2022 18:09:25 +0530 Subject: [PATCH 33/35] chore(llc, core, ui): bump versions to 3.5.0 Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/pubspec.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 2 +- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++++ packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 144cfacc..f8a5bee7 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 3.5.0 ✅ Added diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index e07d416b..6e6893c1 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 3.4.0 +version: 3.5.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d4a64c1a..8aa64cd9 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 3.5.0 🐞 Fixed diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 348b8205..c2d90eb0 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 3.4.0 +version: 3.5.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -36,7 +36,7 @@ dependencies: rxdart: ^0.27.0 share_plus: ^3.0.4 shimmer: ^2.0.0 - stream_chat_flutter_core: ^3.4.0 + stream_chat_flutter_core: ^3.5.0 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index d75d17bd..b90d4e56 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.5.1 + +- Updated `stream_chat` dependency to [`3.5.0`](https://pub.dev/packages/stream_chat/changelog). + ## 3.4.0 - Updated `stream_chat` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat/changelog). diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 24814e97..b9d8e440 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 3.4.0 +version: 3.5.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^3.4.0 + stream_chat: ^3.5.0 dev_dependencies: dart_code_metrics: ^4.4.0 From 1f0bf3bd6b821a8c90a9b9ab50b4cee94ab18cd7 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 1 Mar 2022 19:55:03 +0530 Subject: [PATCH 34/35] Update packages/stream_chat_flutter_core/CHANGELOG.md --- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index b90d4e56..1fa60708 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.5.1 +## 3.5.0 - Updated `stream_chat` dependency to [`3.5.0`](https://pub.dev/packages/stream_chat/changelog). From 7432ae69c03d6133e18f40f533c999648b555192 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 1 Mar 2022 20:01:13 +0530 Subject: [PATCH 35/35] chore(llc): update version.dart to 3.5.0 Signed-off-by: xsahil03x --- packages/stream_chat/lib/version.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 8ec6b487..0ae6a20a 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '3.4.0'; +const PACKAGE_VERSION = '3.5.0';