diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 82225b27..f8a5bee7 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 3.5.0 ✅ Added @@ -10,6 +10,9 @@ - [[#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. +- Fix `channelState.copyWith` with respect to pinnedMessages. ## 3.4.0 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 76fbab7a..0493c0d7 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) { @@ -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!.addMessage(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?.addMessage(message); + state?.deleteMessage(message, hardDelete: hardDelete); final response = await _client.deleteMessage(message.id, hard: hard); - state?.addMessage(message.copyWith(status: MessageSendingStatus.sent)); + state?.deleteMessage( + message.copyWith(status: MessageSendingStatus.sent), + hardDelete: hardDelete, + ); return response; } catch (e) { @@ -861,7 +869,7 @@ class Channel { ownReactions: ownReactions, ); - state?.addMessage(newMessage); + state?.updateMessage(newMessage); try { final reactionResp = await _client.sendReaction( @@ -874,7 +882,7 @@ class Channel { return reactionResp; } catch (_) { // Reset the message if the update fails - state?.addMessage(message); + state?.updateMessage(message); rethrow; } } @@ -914,7 +922,7 @@ class Channel { ownReactions: ownReactions, ); - state?.addMessage(newMessage); + state?.updateMessage(newMessage); try { final deleteResponse = await _client.deleteReaction( @@ -924,7 +932,7 @@ class Channel { return deleteResponse; } catch (_) { // Reset the message if the update fails - state?.addMessage(message); + state?.updateMessage(message); rethrow; } } @@ -1081,7 +1089,7 @@ 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 @@ -1712,7 +1720,7 @@ class ChannelClientState { final message = event.message!.copyWith( ownReactions: ownReactions, ); - addMessage(message); + updateMessage(message); })); } @@ -1725,7 +1733,7 @@ class ChannelClientState { final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); - addMessage(message); + updateMessage(message); })); } @@ -1743,7 +1751,7 @@ class ChannelClientState { final message = event.message!.copyWith( ownReactions: oldMessage?.ownReactions, ); - addMessage(message); + updateMessage(message); if (message.pinned) { _channelState = _channelState.copyWith( @@ -1760,9 +1768,9 @@ class ChannelClientState { _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { final message = event.message!; if (event.hardDelete == true) { - removeMessage(message, hardDelete: true); + removeMessage(message); } else { - addMessage(message); + updateMessage(message); } })); } @@ -1777,7 +1785,7 @@ class ChannelClientState { final message = event.message!; if (isUpToDate || (message.parentId != null && message.showInChannel != true)) { - addMessage(message); + updateMessage(message); } if (_countMessageAsUnread(message)) { @@ -1787,9 +1795,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; @@ -1804,8 +1816,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, ), @@ -1818,41 +1846,35 @@ class ChannelClientState { } /// Remove a [message] from this [channelState]. - void removeMessage(Message message, {bool hardDelete = 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) { - final allMessages = [...messages]; - final parentMessage = allMessages.firstWhereOrNull( - (it) => it.id == parentId, - ); + final newThreads = {...threads}; + // Early return in case the thread is not available + if (!newThreads.containsKey(parentId)) 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; + _threads = newThreads + ..update( + parentId, + (messages) => messages..removeWhere((e) => e.id == message.id), + ); - addMessage(parentMessage.copyWith(replyCount: replyCount - 1)); - updateThreadInfo( - parentId, - threads[parentId]! - ..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); - } + // Early return if the thread message is not shown in channel. + if (message.showInChannel == false) return; } + + // 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() { @@ -1898,11 +1920,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 +2236,7 @@ class ChannelClientState { .toList(); updateChannelState(_channelState.copyWith( - pinnedMessages: pinnedMessages.where(_pinIsValid()).toList(), + pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), messages: expiredMessages, )); } @@ -2250,7 +2273,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/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 { 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..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, @@ -69,7 +69,7 @@ class ChannelState { // FIXME: Use non-nullable by default instead of empty list. pinnedMessages: pinnedMessages == _emptyPinnedMessages ? this.pinnedMessages - : pinnedMessages ?? _emptyPinnedMessages, + : pinnedMessages, watcherCount: watcherCount ?? this.watcherCount, watchers: watchers ?? this.watchers, read: read ?? this.read, 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'; 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..15aea4b1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -2,6 +2,12 @@ 🐞 Fixed +- Mentions overlay now doesn't overflow when not enough height available + +## 3.5.0 + +🐞 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 - Fix `showScrollToBottom` in `MessageListView` not respecting false value. diff --git a/packages/stream_chat_flutter/example/android/app/build.gradle b/packages/stream_chat_flutter/example/android/app/build.gradle index fbd6268e..3974f949 100644 --- a/packages/stream_chat_flutter/example/android/app/build.gradle +++ b/packages/stream_chat_flutter/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -41,7 +41,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/packages/stream_chat_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/stream_chat_flutter/example/android/app/src/main/AndroidManifest.xml index 42c5fa1e..5e2f5ec0 100644 --- a/packages/stream_chat_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/stream_chat_flutter/example/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" + android:exported="true" android:windowSoftInputMode="adjustResize">