From 2f12aa8e3f0da30dc7fa0b8c6921b377ca844782 Mon Sep 17 00:00:00 2001 From: Tyler Jeong Date: Fri, 3 Nov 2023 09:46:55 +0900 Subject: [PATCH] Add 4.1.0. --- CHANGELOG.md | 18 ++ README.md | 2 +- lib/sendbird_chat_sdk.dart | 2 + lib/src/internal/main/chat/chat.dart | 10 +- lib/src/internal/main/chat/chat_channel.dart | 24 +- .../main/chat/chat_event_handler.dart | 2 +- .../collection_manager.dart | 271 ++++++++++------ .../group_channel_collection_manager.dart | 2 +- .../message_collection_manager.dart | 96 +++--- .../main/chat_manager/command_manager.dart | 101 +++--- .../main/chat_manager/event_dispatcher.dart | 1 + .../main/chat_manager/event_manager.dart | 292 ++++++++++-------- .../extensions/group_channel_extensions.dart | 37 ++- .../main/stats/notification_stat.dart | 4 +- .../internal/main/utils/json_converter.dart | 28 ++ .../feed_channel_mark_as_read_request.dart | 36 +++ ...up_channel_mark_as_delivered_request.dart} | 0 ...oup_channel_mark_as_read_all_request.dart} | 4 +- ...channel_scheduled_message_get_request.dart | 3 +- .../channel_file_message_send_request.dart | 6 +- .../message/channel_message_get_request.dart | 5 +- .../message/channel_messages_gap_request.dart | 6 +- .../message/channel_messages_get_request.dart | 10 +- .../channel_user_message_send_request.dart | 5 +- .../http/http_client/response/responses.dart | 16 +- .../http_client/response/responses.g.dart | 36 ++- .../channel/base_channel/base_channel.dart | 52 +++- .../base_channel/base_channel_message.dart | 32 +- .../base_channel_message_meta_array.dart | 16 +- .../channel/feed_channel/feed_channel.dart | 179 +++++++++-- .../channel/group_channel/group_channel.dart | 18 +- .../group_channel/group_channel.g.dart | 52 +++- .../group_channel/group_channel_read.dart | 6 - .../channel/open_channel/open_channel.dart | 10 +- .../channel/open_channel/open_channel.g.dart | 14 + .../public/core/message/admin_message.dart | 10 +- .../public/core/message/admin_message.g.dart | 34 +- lib/src/public/core/message/base_message.dart | 265 +++------------- lib/src/public/core/message/file_message.dart | 14 +- .../public/core/message/file_message.g.dart | 53 +++- .../core/message/notification_message.dart | 107 +++++++ .../core/message/notification_message.g.dart | 71 +++++ lib/src/public/core/message/root_message.dart | 263 ++++++++++++++++ lib/src/public/core/message/user_message.dart | 10 +- .../public/core/message/user_message.g.dart | 41 ++- lib/src/public/core/user/member.dart | 5 +- lib/src/public/core/user/member.g.dart | 19 ++ lib/src/public/main/chat/sendbird_chat.dart | 2 +- .../base_message_collection.dart | 16 +- .../message_collection.dart | 4 + .../notification_collection.dart | 5 + .../notification_collection_handler.dart | 30 +- lib/src/public/main/define/enums.dart | 7 + .../public/main/handler/channel_handler.dart | 18 +- .../main/handler/user_event_handler.dart | 8 - .../model/channel/group_channel_filter.dart | 4 + .../model/channel/notification_category.dart | 1 - .../model/message/message_change_logs.dart | 18 +- .../model/message/message_change_logs.g.dart | 9 +- .../model/message/message_meta_array.dart | 18 ++ lib/src/public/main/model/og/og_image.dart | 4 +- lib/src/public/main/model/og/og_image.g.dart | 9 + .../public/main/model/og/og_meta_data.dart | 4 +- .../public/main/model/og/og_meta_data.g.dart | 8 + .../public/main/model/reaction/reaction.dart | 4 +- .../main/model/reaction/reaction.g.dart | 6 + .../public/main/model/thread/thread_info.dart | 4 +- .../main/model/thread/thread_info.g.dart | 8 + .../channel/group_channel_create_params.dart | 4 + .../channel/group_channel_update_params.dart | 4 + .../channel/open_channel_create_params.dart | 4 + .../channel/open_channel_update_params.dart | 4 + .../message/base_message_fetch_params.dart | 4 + .../message/message_change_logs_params.dart | 4 + .../params/message/message_list_params.dart | 4 + .../message/message_retrieval_params.dart | 4 + .../scheduled_file_message_update_params.dart | 4 + .../scheduled_user_message_update_params.dart | 4 + .../message/threaded_message_list_params.dart | 4 + .../main/params/poll/poll_create_params.dart | 4 + .../main/params/poll/poll_update_params.dart | 4 + .../message/previous_message_list_query.dart | 2 +- pubspec.yaml | 2 +- 83 files changed, 1800 insertions(+), 731 deletions(-) create mode 100644 lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_mark_as_read_request.dart rename lib/src/internal/network/http/http_client/request/channel/group_channel/{group_channel_delivery_request.dart => group_channel_mark_as_delivered_request.dart} (100%) rename lib/src/internal/network/http/http_client/request/channel/group_channel/{group_channel_read_request.dart => group_channel_mark_as_read_all_request.dart} (88%) create mode 100644 lib/src/public/core/message/notification_message.dart create mode 100644 lib/src/public/core/message/notification_message.g.dart create mode 100644 lib/src/public/core/message/root_message.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 560ec8d4..3548d6a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## v4.1.0 (Nov 3, 2023) + +### Features + +#### NotificationMessage +- Added `NotificationMessage` with `notificationId`, `messageStatus` and `notificationData` +- Added `markAsReadBy()`, `logImpression()` and `logCustom()` in `FeedChannel` +- Replaced `BaseMessage? lastMessage` with `NotificationMessage? lastMessage` in `FeedChannel` +- Replaced `List messageList` with `List messageList` in `NotificationCollection` +- Modified `onMessagesAdded()`, `onMessagesUpdated()` and `onMessagesDeleted()` in `NotificationCollectionHandler` +- Modified `onMessageReceived()` and `onChannelChanged()` in `FeedChannelHandler` + +### Deprecated Methods +- Removed `onTotalUnreadMessageCountUpdated()` in `UserEventHandler` + +### Improvements +- Improved stability + ## v4.0.13 (Sep 27, 2023) ### Features diff --git a/README.md b/README.md index 750476f9..2d331872 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Before installing Sendbird Chat SDK, you need to create a Sendbird application o ```yaml dependencies: - sendbird_chat_sdk: ^4.0.13 + sendbird_chat_sdk: ^4.1.0 ``` - Run `flutter pub get` command in your project directory. diff --git a/lib/sendbird_chat_sdk.dart b/lib/sendbird_chat_sdk.dart index 09cbc879..ac152ed7 100644 --- a/lib/sendbird_chat_sdk.dart +++ b/lib/sendbird_chat_sdk.dart @@ -10,6 +10,8 @@ export 'src/public/core/channel/open_channel/open_channel.dart'; export 'src/public/core/message/admin_message.dart'; export 'src/public/core/message/base_message.dart'; export 'src/public/core/message/file_message.dart'; +export 'src/public/core/message/notification_message.dart'; +export 'src/public/core/message/root_message.dart'; export 'src/public/core/message/user_message.dart'; export 'src/public/core/user/member.dart'; export 'src/public/core/user/restricted_user.dart'; diff --git a/lib/src/internal/main/chat/chat.dart b/lib/src/internal/main/chat/chat.dart index f4c80dda..102fa831 100644 --- a/lib/src/internal/main/chat/chat.dart +++ b/lib/src/internal/main/chat/chat.dart @@ -22,8 +22,8 @@ import 'package:sendbird_chat_sdk/src/internal/main/utils/async/async_task.dart' import 'package:sendbird_chat_sdk/src/internal/network/http/api_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_change_logs_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_logs_request.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_delivery_request.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_read_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_delivered_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_read_all_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_total_count_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/invitation/channel_invitation_preference_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/emoji/emoji_category_request.dart'; @@ -58,7 +58,7 @@ part 'chat_notifications.dart'; part 'chat_push.dart'; part 'chat_user.dart'; -const sdkVersion = '4.0.13'; +const sdkVersion = '4.1.0'; // Internal implementation for main class. Do not directly access this class. class Chat with WidgetsBindingObserver { @@ -87,6 +87,7 @@ class Chat with WidgetsBindingObserver { bool? _isObserverRegistered; ConnectivityResult _connectivityResult = ConnectivityResult.none; + int lastMarkAsReadTimestamp; // This allows a value of type T or T? to be treated as a value of type T?. // We use this so that APIs that have become non-nullable can still be used @@ -110,7 +111,8 @@ class Chat with WidgetsBindingObserver { Chat({ required String appId, required SendbirdChatOptions options, - }) : chatId = globalChatId++ { + }) : chatId = globalChatId++, + lastMarkAsReadTimestamp = 0 { chatContext = ChatContext(appId: appId, options: options); channelCache = ChannelCache(); connectionManager = ConnectionManager(chat: this); // WebSocketClient diff --git a/lib/src/internal/main/chat/chat_channel.dart b/lib/src/internal/main/chat/chat_channel.dart index 74cc7c10..987da38e 100644 --- a/lib/src/internal/main/chat/chat_channel.dart +++ b/lib/src/internal/main/chat/chat_channel.dart @@ -51,15 +51,29 @@ extension ChatChannel on Chat { Future markAsReadAll() async { sbLog.i(StackTrace.current); - await apiClient.send( - GroupChannelMarkAsReadRequest(this, userId: chatContext.currentUserId)); + + final now = DateTime.now().millisecondsSinceEpoch; + if (now - lastMarkAsReadTimestamp <= 1000) { + throw MarkAsReadRateLimitExceededException(); + } + lastMarkAsReadTimestamp = now; + + await apiClient.send(GroupChannelMarkAsReadAllRequest(this, + userId: chatContext.currentUserId)); } Future markAsRead({required List channelUrls}) async { sbLog.i(StackTrace.current, 'channelUrls: $channelUrls'); if (channelUrls.isEmpty) throw InvalidParameterException(); - await apiClient.send(GroupChannelMarkAsReadRequest( + + final now = DateTime.now().millisecondsSinceEpoch; + if (now - lastMarkAsReadTimestamp <= 1000) { + throw MarkAsReadRateLimitExceededException(); + } + lastMarkAsReadTimestamp = now; + + await apiClient.send(GroupChannelMarkAsReadAllRequest( this, channelUrls: channelUrls, userId: chatContext.currentUserId, @@ -141,8 +155,8 @@ extension ChatChannel on Chat { } int get subscribedCustomTypeTotalUnreadMessageCount { - final result = - chatContext.unreadMessageCountInfo.customTypes.values.reduce((a, b) => a + b); + final result = chatContext.unreadMessageCountInfo.customTypes.values + .reduce((a, b) => a + b); sbLog.i(StackTrace.current, 'return: $result'); return result; } diff --git a/lib/src/internal/main/chat/chat_event_handler.dart b/lib/src/internal/main/chat/chat_event_handler.dart index b56646ea..787a668a 100644 --- a/lib/src/internal/main/chat/chat_event_handler.dart +++ b/lib/src/internal/main/chat/chat_event_handler.dart @@ -3,7 +3,7 @@ part of 'chat.dart'; extension ChatEventHandler on Chat { - void addChannelHandler(String identifier, BaseChannelHandler handler) { + void addChannelHandler(String identifier, RootChannelHandler handler) { sbLog.i(StackTrace.current, 'identifier: $identifier'); eventManager.addChannelHandler(identifier, handler); } diff --git a/lib/src/internal/main/chat_manager/collection_manager/collection_manager.dart b/lib/src/internal/main/chat_manager/collection_manager/collection_manager.dart index 546c5cc4..b9aa80fd 100644 --- a/lib/src/internal/main/chat_manager/collection_manager/collection_manager.dart +++ b/lib/src/internal/main/chat_manager/collection_manager/collection_manager.dart @@ -30,6 +30,9 @@ class CollectionManager { final Map lastRequestTsForPollChangeLogs = {}; final Map lastRequestTsForMessagesGap = {}; + bool isGroupChannelCollectionsRefreshing = false; + bool isBaseMessageCollectionsRefreshing = false; + CollectionManager({required Chat chat}) : _chat = chat { _chat.eventManager.addInternalChannelHandler( identifierForInternalGroupChannelHandlerForCollectionManager, @@ -44,37 +47,80 @@ class CollectionManager { //------------------------------// // Reconnected //------------------------------// - void onReconnected() { - sbLog.d(StackTrace.current, 'onReconnected()'); - _refreshGroupChannelCollections(); - _refreshBaseMessageCollections(); + Future onLogin() async { + sbLog.d(StackTrace.current); + } + + Future onReconnected() async { + sbLog.d(StackTrace.current); + + await refresh(); + } + + Future refresh() async { + sbLog.d(StackTrace.current); + + List> futures = []; + + futures.add(_refreshGroupChannelCollections()); + futures.add(_refreshBaseMessageCollections()); + + if (futures.isNotEmpty) { + await Future.wait(futures); + } } - void _refreshGroupChannelCollections() { + Future _refreshGroupChannelCollections() async { + isGroupChannelCollectionsRefreshing = true; + + List> futures = []; + if (groupChannelCollections.isNotEmpty) { - requestGroupChannelChangeLogs(); + futures.add(_requestGroupChannelChangeLogs()); } + + if (futures.isNotEmpty) { + await Future.wait(futures); + } + + isGroupChannelCollectionsRefreshing = false; } - void _refreshBaseMessageCollections() { + Future _refreshBaseMessageCollections() async { + isBaseMessageCollectionsRefreshing = true; + + List> futures = []; + for (final collection in baseMessageCollections) { if (collection.isInitialized) { - _requestMessageChangeLogs(collection); - _requestPollChangeLogs(collection); - _requestMessagesGap(collection); + futures.add(_requestMessageChangeLogs(collection)); + futures.add(_requestPollChangeLogs(collection)); + futures.add(_requestMessagesGap(collection)); } } + + if (futures.isNotEmpty) { + await Future.wait(futures); + } + + isBaseMessageCollectionsRefreshing = false; } - void refreshNotificationCollections() { + Future refreshNotificationCollections() async { + List> futures = []; + for (final collection in baseMessageCollections) { if (collection is NotificationCollection) { if (collection.isInitialized) { - _requestMessageChangeLogs(collection); - _requestMessagesGap(collection); + futures.add(_requestMessageChangeLogs(collection)); + futures.add(_requestMessagesGap(collection)); } } } + + if (futures.isNotEmpty) { + await Future.wait(futures); + } } Future refreshNotificationCollection({ @@ -98,6 +144,43 @@ class CollectionManager { await Future.wait(futures); } } + + void markAsReadForFeedChannel(String channelUrl, List? messageIds) { + for (final collection in baseMessageCollections) { + if (collection is NotificationCollection) { + if (collection.isInitialized) { + if (collection.baseChannel.channelUrl == channelUrl) { + // bool isUpdated = false; + + if (messageIds == null) { + // All + for (final message in collection.messageList) { + if (message.messageStatus == NotificationMessageStatus.sent) { + message.messageStatus = NotificationMessageStatus.read; + // isUpdated = true; + } + } + } else { + for (final message in collection.messageList) { + if (messageIds.contains(message.notificationId)) { + if (message.messageStatus == NotificationMessageStatus.sent) { + message.messageStatus = NotificationMessageStatus.read; + // isUpdated = true; + } + } + } + } + + // if (isUpdated) { + // _chat.eventManager + // .notifyReadStatusUpdated(collection.baseChannel); + // } + break; + } + } + } + } + } } //------------------------------// @@ -115,7 +198,7 @@ class InternalGroupChannelHandlerForCollectionManager // BaseChannelHandler - channel //------------------------------// @override - void onMentionReceived(BaseChannel channel, BaseMessage message) { + void onMentionReceived(BaseChannel channel, RootMessage message) { if (channel is GroupChannel) { _collectionManager.sendEventsToGroupChannelCollectionList( eventSource: CollectionEventSource.eventMentionReceived, @@ -288,8 +371,8 @@ class InternalGroupChannelHandlerForCollectionManager // BaseChannelHandler - message //------------------------------// @override - void onMessageReceived(BaseChannel channel, BaseMessage message) async { - if (channel is GroupChannel) { + void onMessageReceived(BaseChannel channel, RootMessage message) async { + if (channel is GroupChannel || channel is FeedChannel) { for (final messageCollection in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { @@ -308,8 +391,8 @@ class InternalGroupChannelHandlerForCollectionManager } @override - void onMessageUpdated(BaseChannel channel, BaseMessage message) async { - if (channel is GroupChannel) { + void onMessageUpdated(BaseChannel channel, RootMessage message) async { + if (channel is GroupChannel || channel is FeedChannel) { for (final messageCollection in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { @@ -352,8 +435,8 @@ class InternalGroupChannelHandlerForCollectionManager in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { - if (message.messageId == event.messageId) { - message.applyReactionEvent(event); + if (message.getMessageId() == event.messageId) { + (message as BaseMessage).applyReactionEvent(event); _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, @@ -379,8 +462,8 @@ class InternalGroupChannelHandlerForCollectionManager in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { - if (message.messageId == event.targetMessageId) { - message.applyThreadInfoUpdateEvent(event); + if (message.getMessageId() == event.targetMessageId) { + (message as BaseMessage).applyThreadInfoUpdateEvent(event); _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, @@ -512,7 +595,7 @@ class InternalGroupChannelHandlerForCollectionManager for (final messageCollection in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { - if (message.messageId == event.messageId) { + if (message.getMessageId() == event.messageId) { if (message is UserMessage && message.poll != null) { message.poll!.applyPollVoteEvent(event); } @@ -537,7 +620,7 @@ class InternalGroupChannelHandlerForCollectionManager for (final messageCollection in _collectionManager.baseMessageCollections) { if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { - if (message.messageId == event.messageId) { + if (message.getMessageId() == event.messageId) { if (message is UserMessage && message.poll != null) { message.poll!.applyPollUpdateEvent(event); } @@ -575,23 +658,23 @@ class InternalFeedChannelHandlerForCollectionManager : _collectionManager = collectionManager; //------------------------------// -// BaseChannelHandler - channel +// FeedChannelHandler - channel //------------------------------// - @override - void onMentionReceived(BaseChannel channel, BaseMessage message) { - if (channel is FeedChannel) { - for (final messageCollection - in _collectionManager.baseMessageCollections) { - if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { - _collectionManager.sendEventsToMessageCollectionList( - eventSource: CollectionEventSource.eventMentionReceived, - updatedChannels: [channel], - ); - break; - } - } - } - } +// @override +// void onMentionReceived(BaseChannel channel, NotificationMessage message) { +// if (channel is FeedChannel) { +// for (final messageCollection +// in _collectionManager.baseMessageCollections) { +// if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { +// _collectionManager.sendEventsToMessageCollectionList( +// eventSource: CollectionEventSource.eventMentionReceived, +// updatedChannels: [channel], +// ); +// break; +// } +// } +// } +// } @override void onChannelChanged(BaseChannel channel) { @@ -609,27 +692,28 @@ class InternalFeedChannelHandlerForCollectionManager } } - @override - void onChannelDeleted(String channelUrl, ChannelType channelType) { - if (channelType == ChannelType.feed) { - for (final messageCollection - in _collectionManager.baseMessageCollections) { - if (messageCollection.baseChannel.channelUrl == channelUrl) { - _collectionManager.sendEventsToMessageCollectionList( - eventSource: CollectionEventSource.eventChannelDeleted, - deletedChannelUrls: [channelUrl], - ); - break; - } - } - } - } + // @override + // void onChannelDeleted(String channelUrl, ChannelType channelType) { + // if (channelType == ChannelType.feed) { + // for (final messageCollection + // in _collectionManager.baseMessageCollections) { + // if (messageCollection.baseChannel.channelUrl == channelUrl) { + // _collectionManager.sendEventsToMessageCollectionList( + // eventSource: CollectionEventSource.eventChannelDeleted, + // deletedChannelUrls: [channelUrl], + // ); + // break; + // } + // } + // } + // } //------------------------------// -// BaseChannelHandler - message +// FeedChannelHandler - message //------------------------------// @override - void onMessageReceived(BaseChannel channel, BaseMessage message) async { + void onMessageReceived( + BaseChannel channel, NotificationMessage message) async { if (channel is FeedChannel) { for (final messageCollection in _collectionManager.baseMessageCollections) { @@ -648,43 +732,44 @@ class InternalFeedChannelHandlerForCollectionManager } } - @override - void onMessageUpdated(BaseChannel channel, BaseMessage message) async { - if (channel is FeedChannel) { - for (final messageCollection - in _collectionManager.baseMessageCollections) { - if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { - _collectionManager.sendEventsToMessageCollection( - messageCollection: messageCollection, - baseChannel: channel, - eventSource: CollectionEventSource.eventMessageUpdated, - sendingStatus: SendingStatus.succeeded, - updatedMessages: [message], - ); - break; - } - } - } - } +// @override +// void onMessageUpdated( +// BaseChannel channel, NotificationMessage message) async { +// if (channel is FeedChannel) { +// for (final messageCollection +// in _collectionManager.baseMessageCollections) { +// if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { +// _collectionManager.sendEventsToMessageCollection( +// messageCollection: messageCollection, +// baseChannel: channel, +// eventSource: CollectionEventSource.eventMessageUpdated, +// sendingStatus: SendingStatus.succeeded, +// updatedMessages: [message], +// ); +// break; +// } +// } +// } +// } - @override - void onMessageDeleted(BaseChannel channel, int messageId) async { - if (channel is FeedChannel) { - for (final messageCollection - in _collectionManager.baseMessageCollections) { - if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { - _collectionManager.sendEventsToMessageCollection( - messageCollection: messageCollection, - baseChannel: channel, - eventSource: CollectionEventSource.eventMessageDeleted, - sendingStatus: SendingStatus.succeeded, - deletedMessageIds: [messageId], - ); - break; - } - } - } - } +// @override +// void onMessageDeleted(BaseChannel channel, String notificationId) async { +// if (channel is FeedChannel) { +// for (final messageCollection +// in _collectionManager.baseMessageCollections) { +// if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { +// _collectionManager.sendEventsToMessageCollection( +// messageCollection: messageCollection, +// baseChannel: channel, +// eventSource: CollectionEventSource.eventMessageDeleted, +// sendingStatus: SendingStatus.succeeded, +// deletedMessageIds: [notificationId], +// ); +// break; +// } +// } +// } +// } //------------------------------// // FeedChannelHandler - channel diff --git a/lib/src/internal/main/chat_manager/collection_manager/group_channel_collection_manager.dart b/lib/src/internal/main/chat_manager/collection_manager/group_channel_collection_manager.dart index c4715dcb..761ea093 100644 --- a/lib/src/internal/main/chat_manager/collection_manager/group_channel_collection_manager.dart +++ b/lib/src/internal/main/chat_manager/collection_manager/group_channel_collection_manager.dart @@ -22,7 +22,7 @@ extension GroupChannelCollectionManager on CollectionManager { //------------------------------// // GroupChannel changeLogs //------------------------------// - void requestGroupChannelChangeLogs( + Future _requestGroupChannelChangeLogs( {CollectionEventSource? eventSource}) async { final params = GroupChannelChangeLogsParams(); final List updatedChannels = []; diff --git a/lib/src/internal/main/chat_manager/collection_manager/message_collection_manager.dart b/lib/src/internal/main/chat_manager/collection_manager/message_collection_manager.dart index d63bb46d..71dca6e7 100644 --- a/lib/src/internal/main/chat_manager/collection_manager/message_collection_manager.dart +++ b/lib/src/internal/main/chat_manager/collection_manager/message_collection_manager.dart @@ -22,7 +22,7 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// // onMessageSentByMe //------------------------------// - void onMessageSentByMe(BaseMessage message) async { + void onMessageSentByMe(RootMessage message) async { sbLog.d(StackTrace.current, 'onMessageSentByMe()'); for (final messageCollection in baseMessageCollections) { @@ -44,8 +44,8 @@ extension MessageCollectionManager on CollectionManager { BaseMessageCollection messageCollection) async { final channel = messageCollection.baseChannel; final params = MessageChangeLogParams(); - final List updatedMessages = []; - final List deletedMessageIds = []; + final List updatedMessages = []; + final List deletedMessageIds = []; MessageChangeLogs changeLogs; String? token; @@ -98,8 +98,8 @@ extension MessageCollectionManager on CollectionManager { } final groupChannel = messageCollection.baseChannel as GroupChannel; - final List updatedMessages = []; - final List deletedMessageIds = []; + final List updatedMessages = []; + final List deletedMessageIds = []; PollChangeLogs changeLogs; String? token; @@ -121,7 +121,7 @@ extension MessageCollectionManager on CollectionManager { changeLogs.updatedPolls!.isNotEmpty) { for (final poll in changeLogs.updatedPolls!) { for (final message in messageCollection.messageList) { - if (message.messageId == poll.messageId) { + if (message.getMessageId() == poll.messageId) { updatedMessages.add(message); break; } @@ -160,7 +160,7 @@ extension MessageCollectionManager on CollectionManager { messageCollection.startingPoint); int nextCacheCount = messageCollection.latestMessage != null ? 1 : 0; - final messages = await _chat.apiClient.send?>( + final messages = await _chat.apiClient.send?>( ChannelMessagesGapRequest( _chat, channelType: ChannelType.group, @@ -218,15 +218,17 @@ extension MessageCollectionManager on CollectionManager { if (updatedChannel.channelUrl == messageCollection.baseChannel.channelUrl) { if (!messageCollection.isDisposed) { - if (messageCollection.baseHandler is MessageCollectionHandler) { + if (messageCollection.baseHandler is MessageCollectionHandler && + updatedChannel is GroupChannel) { (messageCollection.baseHandler as MessageCollectionHandler) - .onChannelUpdated(GroupChannelContext(eventSource), - updatedChannel as GroupChannel); + .onChannelUpdated( + GroupChannelContext(eventSource), updatedChannel); } else if (messageCollection.baseHandler - is NotificationCollectionHandler) { + is NotificationCollectionHandler && + updatedChannel is FeedChannel) { (messageCollection.baseHandler as NotificationCollectionHandler) - .onChannelUpdated(FeedChannelContext(eventSource), - updatedChannel as FeedChannel); + .onChannelUpdated( + FeedChannelContext(eventSource), updatedChannel); } } break; @@ -266,20 +268,20 @@ extension MessageCollectionManager on CollectionManager { required BaseChannel baseChannel, required CollectionEventSource eventSource, required SendingStatus sendingStatus, - List? addedMessages, + List? addedMessages, bool isReversedAddedMessages = false, - List? updatedMessages, - List? deletedMessageIds, + List? updatedMessages, + List? deletedMessageIds, }) { - final List addedMessagesForEvent = []; - final List updatedMessagesForEvent = []; - final List deletedMessagesForEvent = []; + List addedMessagesForEvent = []; + final List updatedMessagesForEvent = []; + final List deletedMessagesForEvent = []; if (addedMessages != null && addedMessages.isNotEmpty) { for (final addedMessage in addedMessages) { bool isMessageExists = false; for (final message in messageCollection.messageList) { - if (message.messageId == addedMessage.messageId) { + if (message.getMessageId() == addedMessage.getMessageId()) { isMessageExists = true; break; } @@ -295,6 +297,14 @@ extension MessageCollectionManager on CollectionManager { } } + if (messageCollection is NotificationCollection) { + addedMessagesForEvent = + List.from(addedMessagesForEvent); + } else { + // MessageCollection + addedMessagesForEvent = List.from(addedMessagesForEvent); + } + if (isReversedAddedMessages) { messageCollection.messageList.insertAll(0, addedMessagesForEvent); } else { @@ -308,7 +318,7 @@ extension MessageCollectionManager on CollectionManager { index < messageCollection.messageList.length; index++) { final message = messageCollection.messageList[index]; - if (message.messageId == updatedMessage.messageId) { + if (message.getMessageId() == updatedMessage.getMessageId()) { if (eventSource == CollectionEventSource.pollChangeLogs || eventSource == CollectionEventSource.eventPollVoted || eventSource == CollectionEventSource.eventPollUpdated) { @@ -349,7 +359,7 @@ extension MessageCollectionManager on CollectionManager { index < messageCollection.messageList.length; index++) { final message = messageCollection.messageList[index]; - if (message.messageId == deletedMessageId) { + if (message.getMessageId() == deletedMessageId) { deletedMessagesForEvent.add(message); messageCollection.messageList.removeAt(index); break; @@ -360,20 +370,22 @@ extension MessageCollectionManager on CollectionManager { if (addedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - if (messageCollection.baseHandler is MessageCollectionHandler) { + if (messageCollection.baseHandler is MessageCollectionHandler && + baseChannel is GroupChannel) { (messageCollection.baseHandler as MessageCollectionHandler) .onMessagesAdded( MessageContext(eventSource, sendingStatus), - baseChannel as GroupChannel, - addedMessagesForEvent, + baseChannel, + List.from(addedMessagesForEvent), ); } else if (messageCollection.baseHandler - is NotificationCollectionHandler) { + is NotificationCollectionHandler && + baseChannel is FeedChannel) { (messageCollection.baseHandler as NotificationCollectionHandler) .onMessagesAdded( NotificationContext(eventSource, sendingStatus), - baseChannel as FeedChannel, - addedMessagesForEvent, + baseChannel, + List.from(addedMessagesForEvent), ); } } @@ -381,20 +393,22 @@ extension MessageCollectionManager on CollectionManager { if (updatedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - if (messageCollection.baseHandler is MessageCollectionHandler) { + if (messageCollection.baseHandler is MessageCollectionHandler && + baseChannel is GroupChannel) { (messageCollection.baseHandler as MessageCollectionHandler) .onMessagesUpdated( MessageContext(eventSource, sendingStatus), - baseChannel as GroupChannel, - updatedMessagesForEvent, + baseChannel, + List.from(updatedMessagesForEvent), ); } else if (messageCollection.baseHandler - is NotificationCollectionHandler) { + is NotificationCollectionHandler && + baseChannel is FeedChannel) { (messageCollection.baseHandler as NotificationCollectionHandler) .onMessagesUpdated( NotificationContext(eventSource, sendingStatus), - baseChannel as FeedChannel, - updatedMessagesForEvent, + baseChannel, + List.from(updatedMessagesForEvent), ); } } @@ -402,20 +416,22 @@ extension MessageCollectionManager on CollectionManager { if (deletedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - if (messageCollection.baseHandler is MessageCollectionHandler) { + if (messageCollection.baseHandler is MessageCollectionHandler && + baseChannel is GroupChannel) { (messageCollection.baseHandler as MessageCollectionHandler) .onMessagesDeleted( MessageContext(eventSource, sendingStatus), - baseChannel as GroupChannel, - deletedMessagesForEvent, + baseChannel, + List.from(deletedMessagesForEvent), ); } else if (messageCollection.baseHandler - is NotificationCollectionHandler) { + is NotificationCollectionHandler && + baseChannel is FeedChannel) { (messageCollection.baseHandler as NotificationCollectionHandler) .onMessagesDeleted( NotificationContext(eventSource, sendingStatus), - baseChannel as FeedChannel, - deletedMessagesForEvent, + baseChannel, + List.from(deletedMessagesForEvent), ); } } diff --git a/lib/src/internal/main/chat_manager/command_manager.dart b/lib/src/internal/main/chat_manager/command_manager.dart index 70a5da4f..df5452c0 100644 --- a/lib/src/internal/main/chat_manager/command_manager.dart +++ b/lib/src/internal/main/chat_manager/command_manager.dart @@ -28,6 +28,8 @@ import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_chan import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/open_channel/open_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/member.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/restricted_user.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; @@ -352,7 +354,7 @@ class CommandManager { }); try { - final message = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( _chat, cmd.payload, commandType: cmd.cmd, @@ -361,10 +363,23 @@ class CommandManager { if (event.requestId != null) { // If sent by api then added id to cache -> done if (message.channelType == ChannelType.group) { - sendCommand(Command.buildMessageMACK( - message.channelUrl, - message.messageId, - )); + dynamic messageId = message.getMessageId(); + if (messageId is int) { + sendCommand(Command.buildMessageMACK( + message.channelUrl, + messageId, + )); + } + } + } + + bool shouldCallChannelChanged = false; + + if (message.channelType == ChannelType.group) { + final cachedChannel = _chat.channelCache + .find(channelKey: message.channelUrl); + if (cachedChannel == null || cachedChannel.dirty) { + shouldCallChannelChanged = true; } } @@ -374,8 +389,6 @@ class CommandManager { chat: _chat, ); - bool shouldCallChannelChanged = false; - final GroupChannel? groupChannel = _eitherGroupOrFeed(channel); if (groupChannel != null) { if (groupChannel.hiddenState == @@ -386,9 +399,16 @@ class CommandManager { groupChannel.updateMember(event.sender); - if (groupChannel.shouldUpdateLastMessage(message, message.sender)) { - shouldCallChannelChanged = true; - groupChannel.lastMessage = message; + if (channel is GroupChannel && message is BaseMessage) { + if (groupChannel.shouldUpdateLastMessage(message, message.sender)) { + shouldCallChannelChanged = true; + groupChannel.lastMessage = message; + } + } else if (channel is FeedChannel && message is NotificationMessage) { + if (channel.shouldUpdateLastMessage(message)) { + shouldCallChannelChanged = true; + channel.lastMessage = message; + } } if (groupChannel.fromCache && groupChannel.updateUnreadCount(message)) { @@ -400,9 +420,11 @@ class CommandManager { _chat.eventManager.notifyMessageReceived(channel, message); } - final currentUser = _chat.chatContext.currentUser; - if (message.mentioned(user: currentUser, byOtherUser: message.sender)) { - _chat.eventManager.notifyMentionReceived(channel, message); + if (channel is GroupChannel && message is BaseMessage) { + final currentUser = _chat.chatContext.currentUser; + if (message.mentioned(user: currentUser, byOtherUser: message.sender)) { + _chat.eventManager.notifyMentionReceived(channel, message); + } } if (shouldCallChannelChanged) { @@ -421,7 +443,7 @@ class CommandManager { }); try { - final message = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( _chat, cmd.payload, commandType: cmd.cmd, @@ -444,31 +466,33 @@ class CommandManager { bool shouldCallChannelChanged = false; bool shouldCallMentionReceived = false; - if (groupChannel.shouldUpdateLastMessage(message, message.sender)) { - shouldCallChannelChanged = true; - groupChannel.lastMessage = message; - } - - final timestamp = groupChannel.myReadReceipt(); - - if (message.hasUpdatedLaterThan(timestamp) && - event.sender?.isCurrentUser == false && - !message.isSilent) { - if (event.hasChangedMentionType() == MentionType.channel && - event.previousMentionedContains(currentUser)) { - if (groupChannel.fromCache) { - groupChannel.increaseUnreadMentionCount(); - } + if (message is BaseMessage) { + if (groupChannel.shouldUpdateLastMessage(message, message.sender)) { shouldCallChannelChanged = true; - shouldCallMentionReceived = true; - } else if (event.hasChangedMentionType() == MentionType.users && - !event.previousMentionedContains(currentUser) && - event.mentionedContains(currentUser)) { - if (groupChannel.fromCache) { - groupChannel.increaseUnreadMentionCount(); + groupChannel.lastMessage = message; + } + + final timestamp = groupChannel.myReadReceipt(); + + if (message.hasUpdatedLaterThan(timestamp) && + event.sender?.isCurrentUser == false && + !message.isSilent) { + if (event.hasChangedMentionType() == MentionType.channel && + event.previousMentionedContains(currentUser)) { + if (groupChannel.fromCache) { + groupChannel.increaseUnreadMentionCount(); + } + shouldCallChannelChanged = true; + shouldCallMentionReceived = true; + } else if (event.hasChangedMentionType() == MentionType.users && + !event.previousMentionedContains(currentUser) && + event.mentionedContains(currentUser)) { + if (groupChannel.fromCache) { + groupChannel.increaseUnreadMentionCount(); + } + shouldCallChannelChanged = true; + shouldCallMentionReceived = true; } - shouldCallChannelChanged = true; - shouldCallMentionReceived = true; } } @@ -1214,7 +1238,8 @@ class CommandManager { groupChannel.pinnedMessageIds = event.data["pinned_message_ids"].cast(); groupChannel.lastPinnedMessage = - BaseMessage.fromJson(event.data["latest_pinned_message"]); + RootMessage.fromJson(event.data["latest_pinned_message"]) + as BaseMessage; } groupChannel.pinnedMessageUpdatedAt = event.ts ?? 0; diff --git a/lib/src/internal/main/chat_manager/event_dispatcher.dart b/lib/src/internal/main/chat_manager/event_dispatcher.dart index fd2717ec..aa418e85 100644 --- a/lib/src/internal/main/chat_manager/event_dispatcher.dart +++ b/lib/src/internal/main/chat_manager/event_dispatcher.dart @@ -16,6 +16,7 @@ class EventDispatcher { Future onLogin(LoginEvent event) async { sbLog.d(StackTrace.current); + _chat.collectionManager.onLogin(); await _chat.statManager.onLogin(event); } diff --git a/lib/src/internal/main/chat_manager/event_manager.dart b/lib/src/internal/main/chat_manager/event_manager.dart index 2ef15f2f..ccd1bce2 100644 --- a/lib/src/internal/main/chat_manager/event_manager.dart +++ b/lib/src/internal/main/chat_manager/event_manager.dart @@ -7,6 +7,8 @@ import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_chan import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/open_channel/open_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/restricted_user.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; @@ -22,7 +24,7 @@ import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction_event. import 'package:sendbird_chat_sdk/src/public/main/model/thread/thread_info_updated_event.dart'; class EventManager { - final Map _channelHandlers = {}; + final Map _channelHandlers = {}; final Map _connectionHandlers = {}; final Map _userHandlers = {}; @@ -34,21 +36,29 @@ class EventManager { EventManager({required SessionManager sessionManager}) : _accessTokenRequester = sessionManager.accessTokenRequester; - void addChannelHandler(String identifier, BaseChannelHandler handler) { - _channelHandlers[identifier] = handler; + void addChannelHandler(String identifier, RootChannelHandler handler) { + if (handler is BaseChannelHandler) { + _channelHandlers[identifier] = handler; + } else if (handler is FeedChannelHandler) { + _channelHandlers[identifier] = handler; + } } void addInternalChannelHandler( - String identifier, BaseChannelHandler handler) { + String identifier, RootChannelHandler handler) { _internalChannelHandlerIdentifiers.add(identifier); - addChannelHandler(identifier, handler); + if (handler is BaseChannelHandler) { + addChannelHandler(identifier, handler); + } else if (handler is FeedChannelHandler) { + addChannelHandler(identifier, handler); + } } BaseChannelHandler? getChannelHandler(String identifier) { if (_internalChannelHandlerIdentifiers.contains(identifier)) { return null; } - return _channelHandlers[identifier]; + return _channelHandlers[identifier] as BaseChannelHandler?; } void removeChannelHandler(String identifier) { @@ -113,21 +123,29 @@ class EventManager { } // BaseChannelHandler - void notifyMessageReceived(BaseChannel channel, BaseMessage message) { + void notifyMessageReceived(BaseChannel channel, RootMessage message) { sbLog.i(StackTrace.current, - '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${message.message}'); - - for (final element in _channelHandlers.values) { - element.onMessageReceived(channel, message); + '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${(message is BaseMessage) ? message.message : ''}'); + + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler && message is BaseMessage) { + e.onMessageReceived(channel, message); + } else if (e is FeedChannelHandler && + channel is FeedChannel && + message is NotificationMessage) { + e.onMessageReceived(channel, message); + } } } - void notifyMessageUpdate(BaseChannel channel, BaseMessage message) { + void notifyMessageUpdate(BaseChannel channel, RootMessage message) { sbLog.i(StackTrace.current, - '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${message.message}'); + '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${(message is BaseMessage) ? message.message : ''}'); - for (final element in _channelHandlers.values) { - element.onMessageUpdated(channel, message); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler && message is BaseMessage) { + e.onMessageUpdated(channel, message); + } } } @@ -135,25 +153,33 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[messageId] $messageId'); - for (final element in _channelHandlers.values) { - element.onMessageDeleted(channel, messageId); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onMessageDeleted(channel, messageId); + } } } - void notifyMentionReceived(BaseChannel channel, BaseMessage message) { + void notifyMentionReceived(BaseChannel channel, RootMessage message) { sbLog.i(StackTrace.current, - '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${message.message}'); + '\n-[channelUrl] ${channel.channelUrl}\n-[message] ${(message is BaseMessage) ? message.message : ''}'); - for (final element in _channelHandlers.values) { - element.onMentionReceived(channel, message); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler && message is BaseMessage) { + e.onMentionReceived(channel, message); + } } } void notifyChannelChanged(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - element.onChannelChanged(channel); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onChannelChanged(channel); + } else if (e is FeedChannelHandler && channel is FeedChannel) { + e.onChannelChanged(channel); + } } } @@ -161,8 +187,10 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] $channelUrl\n-[channelType] $channelType'); - for (final element in _channelHandlers.values) { - element.onChannelDeleted(channelUrl, channelType); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onChannelDeleted(channelUrl, channelType); + } } } @@ -171,8 +199,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onReactionUpdated(channel, event); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onReactionUpdated(channel, event); + } } } @@ -182,8 +212,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onUserMuted(channel, restrictedUser); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onUserMuted(channel, restrictedUser); + } } } @@ -193,8 +225,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onUserUnmuted(channel, user); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onUserUnmuted(channel, user); + } } } @@ -204,8 +238,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onUserBanned(channel, restrictedUser); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onUserBanned(channel, restrictedUser); + } } } @@ -215,8 +251,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onUserUnbanned(channel, user); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onUserUnbanned(channel, user); + } } } @@ -225,8 +263,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onChannelFrozen(channel); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onChannelFrozen(channel); + } } } @@ -235,8 +275,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onChannelUnfrozen(channel); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onChannelUnfrozen(channel); + } } } @@ -246,14 +288,16 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { + for (final e in _channelHandlers.values) { final created = Map.from(data['created'] ?? {}); final updated = Map.from(data['updated'] ?? {}); final deleted = List.from(data['deleted'] ?? []); - if (created.isNotEmpty) element.onMetaDataCreated(channel, created); - if (updated.isNotEmpty) element.onMetaDataUpdated(channel, updated); - if (deleted.isNotEmpty) element.onMetaDataDeleted(channel, deleted); + if (e is BaseChannelHandler) { + if (created.isNotEmpty) e.onMetaDataCreated(channel, created); + if (updated.isNotEmpty) e.onMetaDataUpdated(channel, updated); + if (deleted.isNotEmpty) e.onMetaDataDeleted(channel, deleted); + } } } @@ -264,14 +308,16 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { + for (final e in _channelHandlers.values) { final created = Map.from(data['created'] ?? {}); final updated = Map.from(data['updated'] ?? {}); final deleted = List.from(data['deleted'] ?? []); - if (created.isNotEmpty) element.onMetaCountersCreated(channel, created); - if (updated.isNotEmpty) element.onMetaCountersUpdated(channel, updated); - if (deleted.isNotEmpty) element.onMetaCountersDeleted(channel, deleted); + if (e is BaseChannelHandler) { + if (created.isNotEmpty) e.onMetaCountersCreated(channel, created); + if (updated.isNotEmpty) e.onMetaCountersUpdated(channel, updated); + if (deleted.isNotEmpty) e.onMetaCountersDeleted(channel, deleted); + } } } @@ -280,8 +326,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onOperatorUpdated(channel); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onOperatorUpdated(channel); + } } } @@ -291,8 +339,10 @@ class EventManager { if (channel is FeedChannel) return; - for (final element in _channelHandlers.values) { - element.onThreadInfoUpdated(channel, event); + for (final e in _channelHandlers.values) { + if (e is BaseChannelHandler) { + e.onThreadInfoUpdated(channel, event); + } } } @@ -300,28 +350,22 @@ class EventManager { void notifyReadStatusUpdated(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - if (channel is GroupChannel) { - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onReadStatusUpdated(channel); - } + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler && channel is GroupChannel) { + e.onReadStatusUpdated(channel); } + // else if (e is FeedChannelHandler && channel is FeedChannel) { + // e.onReadStatusUpdated(channel); + // } } - // else if (channel is FeedChannel) { - // for (final element in _channelHandlers.values) { - // if (element is FeedChannelHandler) { - // element.onReadStatusUpdated(channel); - // } - // } - // } } void notifyDeliveryStatusUpdated(GroupChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onDeliveryStatusUpdated(channel); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onDeliveryStatusUpdated(channel); } } } @@ -329,9 +373,9 @@ class EventManager { void notifyChannelTypingStatusUpdated(GroupChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onTypingStatusUpdated(channel); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onTypingStatusUpdated(channel); } } } @@ -341,9 +385,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[invitees] ${invitees.map((e) => e.userId)}, \n-[inviter] ${inviter?.userId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onUserReceivedInvitation(channel, invitees, inviter); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onUserReceivedInvitation(channel, invitees, inviter); } } } @@ -356,9 +400,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[invitee] ${invitee.userId}, \n-[inviter] ${inviter?.userId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onUserDeclinedInvitation(channel, invitee, inviter); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onUserDeclinedInvitation(channel, invitee, inviter); } } } @@ -367,9 +411,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onUserJoined(channel, user); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onUserJoined(channel, user); } } } @@ -378,9 +422,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onUserLeft(channel, user); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onUserLeft(channel, user); } } } @@ -388,9 +432,9 @@ class EventManager { void notifyChannelHidden(GroupChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onChannelHidden(channel); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onChannelHidden(channel); } } } @@ -399,9 +443,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrls] ${channels.map((e) => e.channelUrl)}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onChannelMemberCountChanged(channels); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onChannelMemberCountChanged(channels); } } } @@ -409,9 +453,9 @@ class EventManager { void notifyPollVoted(GroupChannel channel, PollVoteEvent event) { sbLog.i(StackTrace.current, '\n-[pollId] ${event.pollId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onPollVoted(channel, event); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onPollVoted(channel, event); } } } @@ -419,9 +463,9 @@ class EventManager { void notifyPollUpdated(GroupChannel channel, PollUpdateEvent event) { sbLog.i(StackTrace.current, '\n-[pollId] ${event.pollId}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onPollUpdated(channel, event); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onPollUpdated(channel, event); } } } @@ -429,9 +473,9 @@ class EventManager { void notifyPollDeleted(GroupChannel channel, int pollId) { sbLog.i(StackTrace.current, '\n-[pollId] $pollId'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onPollDeleted(channel, pollId); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onPollDeleted(channel, pollId); } } } @@ -439,9 +483,9 @@ class EventManager { void notifyPinnedMessageUpdated(GroupChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onPinnedMessageUpdated(channel); + for (final e in _channelHandlers.values) { + if (e is GroupChannelHandler) { + e.onPinnedMessageUpdated(channel); } } } @@ -451,9 +495,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); - for (final element in _channelHandlers.values) { - if (element is OpenChannelHandler) { - element.onUserEntered(channel, user); + for (final e in _channelHandlers.values) { + if (e is OpenChannelHandler) { + e.onUserEntered(channel, user); } } } @@ -462,9 +506,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); - for (final element in _channelHandlers.values) { - if (element is OpenChannelHandler) { - element.onUserExited(channel, user); + for (final e in _channelHandlers.values) { + if (e is OpenChannelHandler) { + e.onUserExited(channel, user); } } } @@ -473,9 +517,9 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrls] ${channels.map((e) => e.channelUrl)}'); - for (final element in _channelHandlers.values) { - if (element is OpenChannelHandler) { - element.onChannelParticipantCountChanged(channels); + for (final e in _channelHandlers.values) { + if (e is OpenChannelHandler) { + e.onChannelParticipantCountChanged(channels); } } } @@ -484,40 +528,40 @@ class EventManager { void notifyConnected(String userId) { sbLog.i(StackTrace.current, 'userId: $userId'); - for (final element in _connectionHandlers.values) { - element.onConnected(userId); + for (final e in _connectionHandlers.values) { + e.onConnected(userId); } } void notifyDisconnected(String userId) { sbLog.i(StackTrace.current, 'userId: $userId'); - for (final element in _connectionHandlers.values) { - element.onDisconnected(userId); + for (final e in _connectionHandlers.values) { + e.onDisconnected(userId); } } void notifyReconnectStarted() { sbLog.i(StackTrace.current); - for (final element in _connectionHandlers.values) { - element.onReconnectStarted(); + for (final e in _connectionHandlers.values) { + e.onReconnectStarted(); } } void notifyReconnectSucceeded() { sbLog.i(StackTrace.current); - for (final element in _connectionHandlers.values) { - element.onReconnectSucceeded(); + for (final e in _connectionHandlers.values) { + e.onReconnectSucceeded(); } } void notifyReconnectFailed() { sbLog.i(StackTrace.current); - for (final element in _connectionHandlers.values) { - element.onReconnectFailed(); + for (final e in _connectionHandlers.values) { + e.onReconnectFailed(); } } @@ -527,20 +571,16 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[groupChannelUnreadMessageCount] ${unreadMessageCount.totalCountForGroupChannels}\n-[feedChannelUnreadMessageCount] ${unreadMessageCount.totalCountForFeedChannels}\n-[totalCountByCustomType] ${unreadMessageCount.totalCountByCustomType}'); - for (final element in _userHandlers.values) { - element.onTotalUnreadMessageCountChanged(unreadMessageCount); - element.onTotalUnreadMessageCountUpdated( - unreadMessageCount.totalCountForGroupChannels, - unreadMessageCount.totalCountByCustomType, - ); + for (final e in _userHandlers.values) { + e.onTotalUnreadMessageCountChanged(unreadMessageCount); } } void notifyFriendsDiscovered(List friends) { sbLog.i(StackTrace.current, '\n-[friends] ${friends.map((e) => e.userId)}'); - for (final element in _userHandlers.values) { - element.onFriendsDiscovered(friends); + for (final e in _userHandlers.values) { + e.onFriendsDiscovered(friends); } } diff --git a/lib/src/internal/main/extensions/group_channel_extensions.dart b/lib/src/internal/main/extensions/group_channel_extensions.dart index b1556c97..82222f61 100644 --- a/lib/src/internal/main/extensions/group_channel_extensions.dart +++ b/lib/src/internal/main/extensions/group_channel_extensions.dart @@ -21,24 +21,29 @@ extension GroupChannelExtensions on GroupChannel { return false; } - bool updateUnreadCount(BaseMessage message) { + bool updateUnreadCount(RootMessage message) { final currentUser = chat.chatContext.currentUser; - if (!message.isSilent) { - if (message is AdminMessage) { - _increaseUnreadMessageCount(); - return true; - } - - if (message.sender?.isCurrentUser == false) { - _increaseUnreadMessageCount(); - return true; - } - - if (message.mentioned(user: currentUser, byOtherUser: message.sender)) { - increaseUnreadMentionCount(); - return true; + if (message is BaseMessage) { + if (!message.isSilent) { + if (message is AdminMessage) { + _increaseUnreadMessageCount(); + return true; + } + + if (message.sender?.isCurrentUser == false) { + _increaseUnreadMessageCount(); + return true; + } + + if (message.mentioned(user: currentUser, byOtherUser: message.sender)) { + increaseUnreadMentionCount(); + return true; + } } + } else if (message is NotificationMessage) { + _increaseUnreadMessageCount(); + return true; } return false; } @@ -150,7 +155,7 @@ extension GroupChannelExtensions on GroupChannel { void _increaseUnreadMessageCount() { if (canChangeUnreadMessageCount) { unreadMessageCount++; - } else {} + } } void _refreshMemberCounts() { diff --git a/lib/src/internal/main/stats/notification_stat.dart b/lib/src/internal/main/stats/notification_stat.dart index 24022dc2..ab22bf99 100644 --- a/lib/src/internal/main/stats/notification_stat.dart +++ b/lib/src/internal/main/stats/notification_stat.dart @@ -9,7 +9,7 @@ class NotificationStat extends DefaultStat { final String templateKey; final String channelUrl; final List tags; - final int messageId; + final String messageId; final String source; final int messageTs; @@ -65,7 +65,7 @@ class NotificationStat extends DefaultStat { ?.map((tag) => tag as String) .toList() ?? []; - final int? messageId = data['message_id'] as int?; + final String? messageId = data['message_id']; final String? source = data['source'] as String?; final int? messageTs = data['message_ts'] as int?; diff --git a/lib/src/internal/main/utils/json_converter.dart b/lib/src/internal/main/utils/json_converter.dart index 3ce062e0..f835f2ad 100644 --- a/lib/src/internal/main/utils/json_converter.dart +++ b/lib/src/internal/main/utils/json_converter.dart @@ -2,6 +2,8 @@ import 'dart:convert'; +import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; const jsonEncoder = JsonEncoder.withIndent(' '); @@ -9,6 +11,9 @@ const jsonEncoder = JsonEncoder.withIndent(' '); MuteState boolToMuteState(bool? isMuted) => isMuted != null && isMuted ? MuteState.muted : MuteState.unmuted; +bool muteStateToBool(MuteState muteState) => + muteState == MuteState.muted ? true : false; + String userToUserId(Map? userDic) => userDic != null ? userDic['user_id'] ?? userDic['guest_id'] : null; @@ -22,3 +27,26 @@ bool? connectionStatusToBool(UserConnectionStatus status) => status == UserConnectionStatus.notAvailable ? null : status == UserConnectionStatus.online; + +// message +BaseMessage? toNullableBaseMessage(dynamic json) { + if (json == null) return null; + if (json['notification_message_id'] != null) return null; + return RootMessage.fromJson(json) as BaseMessage; +} + +List toBaseMessageList(List json) { + return json.map((e) => RootMessage.fromJson(e) as BaseMessage).toList(); +} + +List toRootMessageList(List json) { + return json.map((e) => RootMessage.fromJson(e)).toList(); +} + +List toDeletedIntMessageIds(List json) { + return json.map((e) => e['message_id'] as int).toList(); +} + +List toDeletedMessageIds(List json) { + return json.map((e) => e['message_id']).toList(); +} diff --git a/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_mark_as_read_request.dart b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_mark_as_read_request.dart new file mode 100644 index 00000000..ce8aab74 --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_mark_as_read_request.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; + +class FeedChannelMarkAsReadRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.put; + + FeedChannelMarkAsReadRequest( + Chat chat, { + required String channelUrl, + int? timestamp, + List? messageIds, + String? userId, + }) : super(chat: chat, userId: userId) { + url = 'group_channels/$channelUrl/messages/mark_as_read'; + body = { + 'channel_url': channelUrl, + }; + + if (timestamp != null) { + body['timestamp'] = timestamp; + } + + if (messageIds != null && messageIds.isNotEmpty) { + body['message_ids'] = messageIds; + } + } + + @override + Future> response(Map res) async { + return res; + } +} diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_delivery_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_delivered_request.dart similarity index 100% rename from lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_delivery_request.dart rename to lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_delivered_request.dart diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_read_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_read_all_request.dart similarity index 88% rename from lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_read_request.dart rename to lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_read_all_request.dart index e2dc3f04..1694ce87 100644 --- a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_read_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_mark_as_read_all_request.dart @@ -5,11 +5,11 @@ import 'package:sendbird_chat_sdk/src/internal/main/utils/string_utils.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; -class GroupChannelMarkAsReadRequest extends ApiRequest { +class GroupChannelMarkAsReadAllRequest extends ApiRequest { @override HttpMethod get method => HttpMethod.put; - GroupChannelMarkAsReadRequest( + GroupChannelMarkAsReadAllRequest( Chat chat, { List? channelUrls, String? userId, diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_get_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_get_request.dart index 2627df16..fde1c437 100644 --- a/lib/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_get_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_get_request.dart @@ -4,6 +4,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/message/scheduled_message_retrieval_params.dart'; @@ -38,6 +39,6 @@ class GroupChannelScheduledMessageGetRequest extends ApiRequest { 'scheduled_at': res['scheduled_at'], }; - return BaseMessage.fromJsonWithChat(chat, res); + return RootMessage.fromJsonWithChat(chat, res) as BaseMessage; } } diff --git a/lib/src/internal/network/http/http_client/request/channel/message/channel_file_message_send_request.dart b/lib/src/internal/network/http/http_client/request/channel/message/channel_file_message_send_request.dart index bf00b268..c3b6d452 100644 --- a/lib/src/internal/network/http/http_client/request/channel/message/channel_file_message_send_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/message/channel_file_message_send_request.dart @@ -5,8 +5,8 @@ import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command_type.dart'; -import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/file_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/message/file_message_create_params.dart'; @@ -48,7 +48,7 @@ class ChannelFileMessageSendRequest extends ApiRequest { @override Future response(Map res) async { - return BaseMessage.getMessageFromJsonWithChat(chat, res, - channelType: channelType); + return RootMessage.getMessageFromJsonWithChat(chat, res, + channelType: channelType) as FileMessage; } } diff --git a/lib/src/internal/network/http/http_client/request/channel/message/channel_message_get_request.dart b/lib/src/internal/network/http/http_client/request/channel/message/channel_message_get_request.dart index a6a302dc..b5e4c370 100644 --- a/lib/src/internal/network/http/http_client/request/channel/message/channel_message_get_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/message/channel_message_get_request.dart @@ -5,6 +5,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/message/message_retrieval_params.dart'; @@ -26,7 +27,7 @@ class ChannelMessageGetRequest extends ApiRequest { } @override - Future response(Map res) async { - return BaseMessage.getMessageFromJsonWithChat(chat, res); + Future response(Map res) async { + return RootMessage.getMessageFromJsonWithChat(chat, res) as BaseMessage; } } diff --git a/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_gap_request.dart b/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_gap_request.dart index 6a0a7641..6c4d9619 100644 --- a/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_gap_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_gap_request.dart @@ -4,7 +4,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; -import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; class ChannelMessagesGapRequest extends ApiRequest { @@ -48,7 +48,7 @@ class ChannelMessagesGapRequest extends ApiRequest { } @override - Future?> response(Map res) async { + Future?> response(Map res) async { final isHugeGap = res['is_huge_gap'] as bool; // final prevMessages = (res['prev_messages'] as List) @@ -56,7 +56,7 @@ class ChannelMessagesGapRequest extends ApiRequest { // channelType: channelType)) // .toList(); final nextMessages = (res['next_messages'] as List) - .map((e) => BaseMessage.getMessageFromJsonWithChat(chat, e, + .map((e) => RootMessage.getMessageFromJsonWithChat(chat, e, channelType: channelType)) .toList(); diff --git a/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart b/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart index 2ab44197..5ae03f2f 100644 --- a/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart @@ -4,7 +4,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; -import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart'; @@ -48,21 +48,21 @@ class ChannelMessagesGetRequest extends ApiRequest { @override Future response(Map res) async { - final baseMessages = (res['messages'] as List) - .map((e) => BaseMessage.getMessageFromJsonWithChat(chat, e, + final messages = (res['messages'] as List) + .map((e) => RootMessage.getMessageFromJsonWithChat(chat, e, channelType: channelType)) .toList(); final hasNext = res['has_next'] as bool?; return ChannelMessagesGetResponse( - messages: List.from(baseMessages), + messages: messages, hasNext: hasNext, ); } } class ChannelMessagesGetResponse { - final List messages; + final List messages; final bool? hasNext; ChannelMessagesGetResponse({ diff --git a/lib/src/internal/network/http/http_client/request/channel/message/channel_user_message_send_request.dart b/lib/src/internal/network/http/http_client/request/channel/message/channel_user_message_send_request.dart index 800fb702..b4768592 100644 --- a/lib/src/internal/network/http/http_client/request/channel/message/channel_user_message_send_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/message/channel_user_message_send_request.dart @@ -6,6 +6,7 @@ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_cli import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command_type.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/user_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/message/user_message_create_params.dart'; @@ -40,7 +41,7 @@ class ChannelUserMessageSendRequest extends ApiRequest { @override Future response(Map res) async { - return BaseMessage.getMessageFromJsonWithChat(chat, res, - channelType: channelType); + return RootMessage.getMessageFromJsonWithChat(chat, res, + channelType: channelType) as BaseMessage; } } diff --git a/lib/src/internal/network/http/http_client/response/responses.dart b/lib/src/internal/network/http/http_client/response/responses.dart index 9b741d52..71ad30a1 100644 --- a/lib/src/internal/network/http/http_client/response/responses.dart +++ b/lib/src/internal/network/http/http_client/response/responses.dart @@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/utils/json_converter.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; @@ -61,7 +62,7 @@ class PollVoterListQueryResponse { _$PollVoterListQueryResponseFromJson(json); } -@JsonSerializable(createToJson: false) +@JsonSerializable() class UserListQueryResponse { @_UserConverter() List users; @@ -81,9 +82,11 @@ class UserListQueryResponse { } return res; } + + Map toJson() => _$UserListQueryResponseToJson(this); } -@JsonSerializable(createToJson: false) +@JsonSerializable() class ChannelListQueryResponse { @JsonKey(name: 'channels') @_ChannelConverter() @@ -104,9 +107,11 @@ class ChannelListQueryResponse { } return res; } + + Map toJson() => _$ChannelListQueryResponseToJson(this); } -@JsonSerializable(createToJson: false) +@JsonSerializable() class FeedChannelListQueryResponse { @JsonKey(name: 'channels') @FeedChannelConverter() @@ -127,10 +132,13 @@ class FeedChannelListQueryResponse { } return res; } + + Map toJson() => _$FeedChannelListQueryResponseToJson(this); } @JsonSerializable(createToJson: false) class MessageSearchQueryResponse { + @JsonKey(fromJson: toBaseMessageList) List results; bool hasNext; @@ -167,7 +175,7 @@ class MetaDataResponse { @JsonSerializable(createToJson: false) class ScheduledMessageResponse { - @JsonKey(defaultValue: []) + @JsonKey(fromJson: toBaseMessageList, defaultValue: []) List scheduledMessages; String? next; diff --git a/lib/src/internal/network/http/http_client/response/responses.g.dart b/lib/src/internal/network/http/http_client/response/responses.g.dart index eb7624f2..5aeeea9c 100644 --- a/lib/src/internal/network/http/http_client/response/responses.g.dart +++ b/lib/src/internal/network/http/http_client/response/responses.g.dart @@ -47,6 +47,13 @@ UserListQueryResponse _$UserListQueryResponseFromJson( next: json['next'] as String?, ); +Map _$UserListQueryResponseToJson( + UserListQueryResponse instance) => + { + 'users': instance.users.map(_UserConverter().toJson).toList(), + 'next': instance.next, + }; + ChannelListQueryResponse _$ChannelListQueryResponseFromJson( Map json) => @@ -58,6 +65,13 @@ ChannelListQueryResponse next: json['next'] as String?, ); +Map _$ChannelListQueryResponseToJson( + ChannelListQueryResponse instance) => + { + 'channels': instance.channels.map(_ChannelConverter().toJson).toList(), + 'next': instance.next, + }; + FeedChannelListQueryResponse _$FeedChannelListQueryResponseFromJson( Map json) => FeedChannelListQueryResponse( @@ -68,13 +82,20 @@ FeedChannelListQueryResponse _$FeedChannelListQueryResponseFromJson( next: json['next'] as String?, ); +Map _$FeedChannelListQueryResponseToJson( + FeedChannelListQueryResponse instance) => + { + 'channels': + instance.channels.map(const FeedChannelConverter().toJson).toList(), + 'next': instance.next, + }; + MessageSearchQueryResponse _$MessageSearchQueryResponseFromJson( Map json) => MessageSearchQueryResponse( - results: (json['results'] as List?) - ?.map((e) => BaseMessage.fromJson(e as Map)) - .toList() ?? - const [], + results: json['results'] == null + ? const [] + : toBaseMessageList(json['results'] as List), hasNext: json['has_next'] as bool? ?? false, totalCount: json['total_count'] as int? ?? -1, next: json['end_cursor'] as String?, @@ -92,10 +113,9 @@ MetaDataResponse _$MetaDataResponseFromJson(Map json) => ScheduledMessageResponse _$ScheduledMessageResponseFromJson( Map json) => ScheduledMessageResponse( - scheduledMessages: (json['scheduled_messages'] as List?) - ?.map((e) => BaseMessage.fromJson(e as Map)) - .toList() ?? - [], + scheduledMessages: json['scheduled_messages'] == null + ? [] + : toBaseMessageList(json['scheduled_messages'] as List), next: json['next'] as String?, ); diff --git a/lib/src/public/core/channel/base_channel/base_channel.dart b/lib/src/public/core/channel/base_channel/base_channel.dart index f2703853..091d5170 100644 --- a/lib/src/public/core/channel/base_channel/base_channel.dart +++ b/lib/src/public/core/channel/base_channel/base_channel.dart @@ -1,7 +1,9 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; @@ -44,6 +46,7 @@ import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_ch import 'package:sendbird_chat_sdk/src/public/core/channel/open_channel/open_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/file_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/user_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; @@ -173,18 +176,16 @@ abstract class BaseChannel implements Cacheable { isEphemeral = false, this.fromCache = false, this.dirty = false, - }) - : _coverUrl = coverUrl, + }) : _coverUrl = coverUrl, _data = data, _customType = customType, _isFrozen = isFrozen, _isEphemeral = isEphemeral; /// ChannelType - ChannelType get channelType => - this is GroupChannel - ? ChannelType.group - : this is OpenChannel + ChannelType get channelType => this is GroupChannel + ? ChannelType.group + : this is OpenChannel ? ChannelType.open : ChannelType.feed; @@ -214,10 +215,11 @@ abstract class BaseChannel implements Cacheable { } } - static Future getBaseChannel(ChannelType channelType, - String channelUrl, { - Chat? chat, - }) async { + static Future getBaseChannel( + ChannelType channelType, + String channelUrl, { + Chat? chat, + }) async { switch (channelType) { case ChannelType.group: return GroupChannel.getChannel(channelUrl, chat: chat); @@ -228,10 +230,11 @@ abstract class BaseChannel implements Cacheable { } } - static Future refreshChannel(ChannelType channelType, - String channelUrl, { - Chat? chat, - }) async { + static Future refreshChannel( + ChannelType channelType, + String channelUrl, { + Chat? chat, + }) async { switch (channelType) { case ChannelType.group: return GroupChannel.refresh(channelUrl, chat: chat); @@ -264,8 +267,7 @@ abstract class BaseChannel implements Cacheable { } @override - int get hashCode => - Object.hash( + int get hashCode => Object.hash( channelUrl, name, coverUrl, @@ -298,4 +300,22 @@ abstract class BaseChannel implements Cacheable { fromCache = other.fromCache; dirty = other.dirty; } + + Map toJson(); + + Uint8List serialize() { + return Uint8List.fromList(jsonEncode(toJson()).codeUnits); + } + + static BaseChannel? buildFromSerializedData(Uint8List data) { + final json = jsonDecode(String.fromCharCodes(data)); + if (json['channel_type'] == ChannelType.group.name) { + return GroupChannel.fromJson(jsonDecode(String.fromCharCodes(data))); + } else if (json['channel_type'] == ChannelType.open.name) { + return OpenChannel.fromJson(jsonDecode(String.fromCharCodes(data))); + } else if (json['channel_type'] == ChannelType.feed.name) { + return FeedChannel.fromJson(jsonDecode(String.fromCharCodes(data))); + } + return null; + } } diff --git a/lib/src/public/core/channel/base_channel/base_channel_message.dart b/lib/src/public/core/channel/base_channel/base_channel_message.dart index 0ef2d7c3..c1c11cea 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_message.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_message.dart @@ -62,12 +62,12 @@ extension BaseChannelMessage on BaseChannel { ); final pendingUserMessage = - BaseMessage.getMessageFromJsonWithChat( + RootMessage.getMessageFromJsonWithChat( chat, cmd.payload, channelType: channelType, commandType: cmd.cmd, - ); + ) as UserMessage; if (chat.chatContext.currentUser == null) { final error = ConnectionRequiredException(); @@ -87,11 +87,11 @@ extension BaseChannelMessage on BaseChannel { chat.commandManager.sendCommand(cmd).then((result) { if (result == null) return; - final message = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( chat, result.payload, commandType: result.cmd, - ); + ) as UserMessage; chat.collectionManager.onMessageSentByMe(message); if (handler != null) handler(message, null); @@ -155,12 +155,12 @@ extension BaseChannelMessage on BaseChannel { final result = await chat.commandManager.sendCommand(cmd); if (result != null) { - final baseMessage = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( chat, result.payload, commandType: cmd.cmd, - ); - return baseMessage; + ) as UserMessage; + return message; } else { throw WebSocketFailedException(); } @@ -229,12 +229,12 @@ extension BaseChannelMessage on BaseChannel { thumbnails: uploadResponse?.thumbnails, ); - final message = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( chat, cmd.payload, channelType: channelType, commandType: cmd.cmd, - ); + ) as FileMessage; if (chat.chatContext.currentUser == null) { final error = ConnectionRequiredException(); @@ -250,11 +250,11 @@ extension BaseChannelMessage on BaseChannel { if (result == null) return; final message = - BaseMessage.getMessageFromJsonWithChat( + RootMessage.getMessageFromJsonWithChat( chat, result.payload, commandType: result.cmd, - ); + ) as FileMessage; chat.collectionManager.onMessageSentByMe(message); if (handler != null) handler(message, null); @@ -380,12 +380,12 @@ extension BaseChannelMessage on BaseChannel { final result = await chat.commandManager.sendCommand(cmd); if (result != null) { - final baseMessage = BaseMessage.getMessageFromJsonWithChat( + final message = RootMessage.getMessageFromJsonWithChat( chat, result.payload, commandType: cmd.cmd, - ); - return baseMessage; + ) as FileMessage; + return message; } else { throw WebSocketFailedException(); } @@ -473,7 +473,7 @@ extension BaseChannelMessage on BaseChannel { /// Retrieves previous or next messages based on the timestamp in a specific channel. /// /// The [timestamp] to be the reference point for messages to retrieve, in Unix milliseconds format. - Future> getMessagesByTimestamp( + Future> getMessagesByTimestamp( int timestamp, MessageListParams params, ) async { @@ -501,7 +501,7 @@ extension BaseChannelMessage on BaseChannel { /// Retrieves previous or next messages based on the message ID in a specific channel. /// /// The [messageId] to be the reference point for messages to retrieve. - Future> getMessagesByMessageId( + Future> getMessagesByMessageId( int messageId, MessageListParams params, ) async { diff --git a/lib/src/public/core/channel/base_channel/base_channel_message_meta_array.dart b/lib/src/public/core/channel/base_channel/base_channel_message_meta_array.dart index d749dc14..3ab77c5b 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_message_meta_array.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_message_meta_array.dart @@ -27,8 +27,8 @@ extension BaseChannelMessageMetaArray on BaseChannel { var result = await chat.commandManager.sendCommand(cmd); if (result != null) { - return BaseMessage.getMessageFromJsonWithChat(chat, result.payload, - commandType: result.cmd); + return RootMessage.getMessageFromJsonWithChat(chat, result.payload, + commandType: result.cmd) as BaseMessage; } else { throw WebSocketFailedException(); } @@ -57,8 +57,8 @@ extension BaseChannelMessageMetaArray on BaseChannel { var result = await chat.commandManager.sendCommand(cmd); if (result != null) { - return BaseMessage.getMessageFromJsonWithChat(chat, result.payload, - commandType: result.cmd); + return RootMessage.getMessageFromJsonWithChat(chat, result.payload, + commandType: result.cmd) as BaseMessage; } else { throw WebSocketFailedException(); } @@ -85,8 +85,8 @@ extension BaseChannelMessageMetaArray on BaseChannel { var result = await chat.commandManager.sendCommand(cmd); if (result != null) { - return BaseMessage.getMessageFromJsonWithChat(chat, result.payload, - commandType: result.cmd); + return RootMessage.getMessageFromJsonWithChat(chat, result.payload, + commandType: result.cmd) as BaseMessage; } else { throw WebSocketFailedException(); } @@ -113,8 +113,8 @@ extension BaseChannelMessageMetaArray on BaseChannel { var result = await chat.commandManager.sendCommand(cmd); if (result != null) { - return BaseMessage.getMessageFromJsonWithChat(chat, result.payload, - commandType: result.cmd); + return RootMessage.getMessageFromJsonWithChat(chat, result.payload, + commandType: result.cmd) as BaseMessage; } else { throw WebSocketFailedException(); } diff --git a/lib/src/public/core/channel/feed_channel/feed_channel.dart b/lib/src/public/core/channel/feed_channel/feed_channel.dart index 19368cdd..41b15fe1 100644 --- a/lib/src/public/core/channel/feed_channel/feed_channel.dart +++ b/lib/src/public/core/channel/feed_channel/feed_channel.dart @@ -3,22 +3,28 @@ import 'package:collection/collection.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/cache_service.dart'; import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/model/read_status.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/stats/sendbird_statistics.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_mark_as_read_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_refresh_request.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; -import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/member.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; -import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/channel/notification_category.dart'; /// Represents a feed channel. /// @since 4.0.3 class FeedChannel extends BaseChannel { + static const _logImpressionMessagesLimit = 30; + static const _logCustomMessagesLimit = 30; + static const _logCustomTopicLengthLimit = 15; + /// The unique channel URL. /// @since 4.0.3 @override @@ -59,14 +65,14 @@ class FeedChannel extends BaseChannel { /// @since 4.0.3 int get myLastRead => groupChannel.myLastRead; - /// The last message of the channel. - /// @since 4.0.3 - BaseMessage? get lastMessage => groupChannel.lastMessage; - /// The unread message count for this channel for the current [User]. /// @since 4.0.3 int get unreadMessageCount => groupChannel.unreadMessageCount; + /// The last message of the channel. + /// @since 4.1.0 + NotificationMessage? lastMessage; + /// isTemplateLabelEnabled /// @since 4.0.6 bool? isTemplateLabelEnabled; @@ -81,15 +87,14 @@ class FeedChannel extends BaseChannel { List notificationCategories; GroupChannel groupChannel; - int _lastMarkAsReadTimestamp; FeedChannel({ required this.groupChannel, - this.isCategoryFilterEnabled, + this.lastMessage, this.isTemplateLabelEnabled, + this.isCategoryFilterEnabled, List? notificationCategories, }) : notificationCategories = notificationCategories ?? [], - _lastMarkAsReadTimestamp = 0, super( channelUrl: groupChannel.channelUrl, name: groupChannel.name, @@ -112,6 +117,13 @@ class FeedChannel extends BaseChannel { ..set(SendbirdChat().chat); // Set the singleton chat } + @override + Map toJson() { + final json = groupChannel.toJson(); + json['channel_type'] = ChannelType.feed.name; // Check + return json; + } + static FeedChannel _fromJson(Chat chat, Map json) { List? notificationCategories; List? categories = json['categories'] as List?; @@ -139,8 +151,11 @@ class FeedChannel extends BaseChannel { return FeedChannel( groupChannel: GroupChannel.fromJsonWithChat(chat, json), - isCategoryFilterEnabled: json['is_category_filter_enabled'] as bool?, + lastMessage: json['last_message'] != null + ? NotificationMessage.fromJsonWithChat(chat, json['last_message']) + : null, isTemplateLabelEnabled: json['is_template_label_enabled'] as bool?, + isCategoryFilterEnabled: json['is_category_filter_enabled'] as bool?, notificationCategories: notificationCategories, ); } @@ -186,18 +201,142 @@ class FeedChannel extends BaseChannel { } /// Sends mark as read to this channel. - /// @since 4.0.3 + /// @since 4.1.0 Future markAsRead() async { sbLog.i(StackTrace.current); - final now = DateTime.now().millisecondsSinceEpoch; - if (now - _lastMarkAsReadTimestamp <= 1000) { - throw MarkAsReadRateLimitExceededException(); + final res = await chat.apiClient + .send>(FeedChannelMarkAsReadRequest( + chat, + channelUrl: channelUrl, + )); + final ts = res['ts'] ?? 0; + + chat.collectionManager.markAsReadForFeedChannel(channelUrl, null); + + if (chat.currentUser != null) { + final status = ReadStatus( + userId: chat.currentUser!.userId, + timestamp: ts, + channelUrl: channelUrl, + channelType: channelType, + ); + status.saveToCache(chat); } + groupChannel.myLastRead = ts; - _lastMarkAsReadTimestamp = now; - final cmd = Command.buildRead(channelUrl); - await chat.commandManager.sendCommand(cmd); + if (groupChannel.unreadMessageCount > 0 || + groupChannel.unreadMentionCount > 0) { + groupChannel.clearUnreadCount(); + // chat.eventManager.notifyChannelChanged(this); // Refer to [_processChannelPropChanged] + } + } + + /// Sends mark as read to this channel by messages. + /// @since 4.1.0 + Future markAsReadBy(List messages) async { + sbLog.i(StackTrace.current); + + final List messageIds = []; + for (final message in messages) { + if (message.messageStatus == NotificationMessageStatus.sent) { + messageIds.add(message.notificationId); + } + } + if (messageIds.isEmpty) return; + + final res = await chat.apiClient + .send>(FeedChannelMarkAsReadRequest( + chat, + channelUrl: channelUrl, + messageIds: messageIds, + )); + final unreadMessageCount = res['unread_message_count']; + + chat.collectionManager.markAsReadForFeedChannel(channelUrl, messageIds); + + if (unreadMessageCount != null && + unreadMessageCount != groupChannel.unreadMessageCount) { + groupChannel.unreadMessageCount = unreadMessageCount; + // chat.eventManager.notifyChannelChanged(this); // Refer to [_processChannelPropChanged] + } + } + + /// logImpression + /// @since 4.1.0 + Future logImpression(List messages) async { + if (messages.isNotEmpty && messages.length <= _logImpressionMessagesLimit) { + bool result = true; + for (final message in messages) { + final Map data = { + 'action': 'impression', + 'template_key': message.notificationData?.templateKey ?? '', + 'channel_url': message.channelUrl, + 'tags': message.notificationData?.tags ?? [], + 'message_id': message.notificationId, + 'source': 'notification', + 'message_ts': message.createdAt, + }; + + if (!await SendbirdStatistics.appendStat( + type: 'noti:stats', + data: data, + )) { + result = false; + } + } + return result; + } + return false; + } + + /// logCustom + /// @since 4.1.0 + Future logCustom( + List messages, + String topic, + ) async { + if (messages.isNotEmpty && + messages.length <= _logCustomMessagesLimit && + topic.isNotEmpty && + topic.length <= _logCustomTopicLengthLimit) { + bool result = true; + for (final message in messages) { + final Map data = { + 'action': 'custom', + 'topic': topic, + 'template_key': message.notificationData?.templateKey ?? '', + 'channel_url': message.channelUrl, + 'tags': message.notificationData?.tags ?? [], + 'message_id': message.notificationId, + 'source': 'notification', + 'message_ts': message.createdAt, + }; + + if (!await SendbirdStatistics.appendStat( + type: 'noti:stats', + data: data, + )) { + result = false; + } + } + return result; + } + return false; + } + + bool shouldUpdateLastMessage(NotificationMessage message) { + final lm = lastMessage; + if (lm == null) { + return true; + } else if (lm.createdAt < message.createdAt) { + return true; + } else if (lm.createdAt == message.createdAt && + lm.notificationId == message.notificationId && + lm.updatedAt < message.updatedAt) { + return true; + } + return false; } @override @@ -208,6 +347,7 @@ class FeedChannel extends BaseChannel { final eq = const ListEquality().equals; return other is FeedChannel && other.groupChannel == groupChannel && + other.lastMessage == lastMessage && other.isTemplateLabelEnabled == isTemplateLabelEnabled && other.isCategoryFilterEnabled == isCategoryFilterEnabled && eq(other.notificationCategories, notificationCategories); @@ -217,6 +357,7 @@ class FeedChannel extends BaseChannel { int get hashCode => Object.hash( super.hashCode, groupChannel.hashCode, + lastMessage, isTemplateLabelEnabled, isCategoryFilterEnabled, notificationCategories, @@ -227,7 +368,7 @@ class FeedChannel extends BaseChannel { super.copyWith(other); if (other is FeedChannel) { groupChannel.copyWith(other.groupChannel); - + lastMessage = other.lastMessage; isTemplateLabelEnabled = other.isTemplateLabelEnabled; isCategoryFilterEnabled = other.isCategoryFilterEnabled; notificationCategories = diff --git a/lib/src/public/core/channel/group_channel/group_channel.dart b/lib/src/public/core/channel/group_channel/group_channel.dart index 7b6e036c..ed2c690c 100644 --- a/lib/src/public/core/channel/group_channel/group_channel.dart +++ b/lib/src/public/core/channel/group_channel/group_channel.dart @@ -48,6 +48,8 @@ import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_chan import 'package:sendbird_chat_sdk/src/public/core/message/admin_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/file_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/user_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/member.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; @@ -79,9 +81,10 @@ part 'group_channel_typing.dart'; part 'package:sendbird_chat_sdk/src/internal/main/extensions/group_channel_extensions.dart'; /// Represents a group channel. -@JsonSerializable(createToJson: false) +@JsonSerializable() class GroupChannel extends BaseChannel { /// The last message of the channel. + @JsonKey(fromJson: toNullableBaseMessage) BaseMessage? lastMessage; /// Checks whether this channel is a super [GroupChannel]. @@ -145,7 +148,7 @@ class GroupChannel extends BaseChannel { Role myRole; /// My muted state in this channel. - @JsonKey(fromJson: boolToMuteState, name: 'is_muted') + @JsonKey(fromJson: boolToMuteState, toJson: muteStateToBool, name: 'is_muted') MuteState myMutedState; /// Checks whether unread message count is enabled for this channel. @@ -189,7 +192,7 @@ class GroupChannel extends BaseChannel { List pinnedMessageIds; /// The last message among channel's pinned messages. - @JsonKey(name: 'latest_pinned_message') + @JsonKey(fromJson: toNullableBaseMessage, name: 'latest_pinned_message') BaseMessage? lastPinnedMessage; @JsonKey(includeFromJson: false, includeToJson: false) @@ -197,7 +200,6 @@ class GroupChannel extends BaseChannel { int _lastStartTypingTimestamp; int _lastEndTypingTimestamp; - int _lastMarkAsReadTimestamp; GroupChannel({ required String channelUrl, @@ -241,7 +243,6 @@ class GroupChannel extends BaseChannel { bool isEphemeral = false, }) : _lastStartTypingTimestamp = 0, _lastEndTypingTimestamp = 0, - _lastMarkAsReadTimestamp = 0, super( channelUrl: channelUrl, name: name, @@ -377,6 +378,13 @@ class GroupChannel extends BaseChannel { return GroupChannel.fromJson(json)..set(chat); } + @override + Map toJson() { + final json = _$GroupChannelToJson(this); + json['channel_type'] = ChannelType.group.name; // Check + return json; + } + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/core/channel/group_channel/group_channel.g.dart b/lib/src/public/core/channel/group_channel/group_channel.g.dart index 34906bc0..5deeed55 100644 --- a/lib/src/public/core/channel/group_channel/group_channel.g.dart +++ b/lib/src/public/core/channel/group_channel/group_channel.g.dart @@ -8,9 +8,7 @@ part of 'group_channel.dart'; GroupChannel _$GroupChannelFromJson(Map json) => GroupChannel( channelUrl: json['channel_url'] as String, - lastMessage: json['last_message'] == null - ? null - : BaseMessage.fromJson(json['last_message'] as Map), + lastMessage: toNullableBaseMessage(json['last_message']), isSuper: json['is_super'] as bool? ?? false, isBroadcast: json['is_broadcast'] as bool? ?? false, isPublic: json['is_public'] as bool? ?? false, @@ -63,10 +61,7 @@ GroupChannel _$GroupChannelFromJson(Map json) => GroupChannel( ?.map((e) => e as int) .toList() ?? const [], - lastPinnedMessage: json['latest_pinned_message'] == null - ? null - : BaseMessage.fromJson( - json['latest_pinned_message'] as Map), + lastPinnedMessage: toNullableBaseMessage(json['latest_pinned_message']), name: json['name'] as String? ?? '', coverUrl: json['cover_url'] as String? ?? '', createdAt: json['created_at'] as int?, @@ -76,6 +71,49 @@ GroupChannel _$GroupChannelFromJson(Map json) => GroupChannel( isEphemeral: json['is_ephemeral'] as bool? ?? false, ); +Map _$GroupChannelToJson(GroupChannel instance) => + { + 'channel_url': instance.channelUrl, + 'name': instance.name, + 'created_at': instance.createdAt, + 'cover_url': instance.coverUrl, + 'data': instance.data, + 'custom_type': instance.customType, + 'freeze': instance.isFrozen, + 'is_ephemeral': instance.isEphemeral, + 'last_message': instance.lastMessage?.toJson(), + 'is_super': instance.isSuper, + 'is_broadcast': instance.isBroadcast, + 'is_public': instance.isPublic, + 'is_distinct': instance.isDistinct, + 'is_discoverable': instance.isDiscoverable, + 'is_exclusive': instance.isExclusive, + 'is_access_code_required': instance.isAccessCodeRequired, + 'unread_message_count': instance.unreadMessageCount, + 'unread_mention_count': instance.unreadMentionCount, + 'members': instance.members.map((e) => e.toJson()).toList(), + 'member_count': instance.memberCount, + 'joined_member_count': instance.joinedMemberCount, + 'push_trigger_option': + _$GroupChannelPushTriggerOptionEnumMap[instance.myPushTriggerOption]!, + 'is_chat_notification': instance.isChatNotification, + 'member_state': _$MemberStateEnumMap[instance.myMemberState]!, + 'my_role': _$RoleEnumMap[instance.myRole]!, + 'is_muted': muteStateToBool(instance.myMutedState), + 'count_preference': _$CountPreferenceEnumMap[instance.myCountPreference]!, + 'created_by': instance.creator?.toJson(), + 'inviter': instance.inviter?.toJson(), + 'invited_at': instance.invitedAt, + 'joined_ts': instance.joinedAt, + 'is_hidden': instance.isHidden, + 'hidden_state': _$GroupChannelHiddenStateEnumMap[instance.hiddenState]!, + 'user_last_read': instance.myLastRead, + 'ts_message_offset': instance.messageOffsetTimestamp, + 'message_survival_seconds': instance.messageSurvivalSeconds, + 'pinned_message_ids': instance.pinnedMessageIds, + 'latest_pinned_message': instance.lastPinnedMessage?.toJson(), + }; + const _$GroupChannelPushTriggerOptionEnumMap = { GroupChannelPushTriggerOption.defaultValue: 'default', GroupChannelPushTriggerOption.all: 'all', diff --git a/lib/src/public/core/channel/group_channel/group_channel_read.dart b/lib/src/public/core/channel/group_channel/group_channel_read.dart index a3b3915b..fb9abe33 100644 --- a/lib/src/public/core/channel/group_channel/group_channel_read.dart +++ b/lib/src/public/core/channel/group_channel/group_channel_read.dart @@ -8,12 +8,6 @@ extension GroupChannelRead on GroupChannel { Future markAsRead() async { sbLog.i(StackTrace.current); - final now = DateTime.now().millisecondsSinceEpoch; - if (now - _lastMarkAsReadTimestamp <= 1000) { - throw MarkAsReadRateLimitExceededException(); - } - - _lastMarkAsReadTimestamp = now; final cmd = Command.buildRead(channelUrl); await chat.commandManager.sendCommand(cmd); } diff --git a/lib/src/public/core/channel/open_channel/open_channel.dart b/lib/src/public/core/channel/open_channel/open_channel.dart index ed626c1d..28d93b22 100644 --- a/lib/src/public/core/channel/open_channel/open_channel.dart +++ b/lib/src/public/core/channel/open_channel/open_channel.dart @@ -14,6 +14,7 @@ import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/open_channel_create_params.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/open_channel_update_params.dart'; @@ -21,7 +22,7 @@ part 'open_channel.g.dart'; part 'open_channel_operation.dart'; /// Represents an open channel. -@JsonSerializable(createToJson: false) +@JsonSerializable() class OpenChannel extends BaseChannel { /// The total number of participants in this channel. int participantCount; @@ -142,6 +143,13 @@ class OpenChannel extends BaseChannel { return OpenChannel.fromJson(json)..set(chat); } + @override + Map toJson() { + final json = _$OpenChannelToJson(this); + json['channel_type'] = ChannelType.open.name; // Check + return json; + } + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/core/channel/open_channel/open_channel.g.dart b/lib/src/public/core/channel/open_channel/open_channel.g.dart index 2d258b06..eb3d39bf 100644 --- a/lib/src/public/core/channel/open_channel/open_channel.g.dart +++ b/lib/src/public/core/channel/open_channel/open_channel.g.dart @@ -21,3 +21,17 @@ OpenChannel _$OpenChannelFromJson(Map json) => OpenChannel( isFrozen: json['freeze'] as bool? ?? false, isEphemeral: json['is_ephemeral'] as bool? ?? false, ); + +Map _$OpenChannelToJson(OpenChannel instance) => + { + 'channel_url': instance.channelUrl, + 'name': instance.name, + 'created_at': instance.createdAt, + 'cover_url': instance.coverUrl, + 'data': instance.data, + 'custom_type': instance.customType, + 'freeze': instance.isFrozen, + 'is_ephemeral': instance.isEphemeral, + 'participant_count': instance.participantCount, + 'operators': instance.operators.map((e) => e.toJson()).toList(), + }; diff --git a/lib/src/public/core/message/admin_message.dart b/lib/src/public/core/message/admin_message.dart index 8655f659..0de4365f 100644 --- a/lib/src/public/core/message/admin_message.dart +++ b/lib/src/public/core/message/admin_message.dart @@ -4,6 +4,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; @@ -16,7 +17,7 @@ import 'package:sendbird_chat_sdk/src/public/main/model/thread/thread_info.dart' part 'admin_message.g.dart'; /// Object representing an admin message. -@JsonSerializable(createToJson: false) +@JsonSerializable() class AdminMessage extends BaseMessage { AdminMessage({ required int messageId, @@ -79,4 +80,11 @@ class AdminMessage extends BaseMessage { factory AdminMessage.fromJsonWithChat(Chat chat, Map json) { return AdminMessage.fromJson(json)..set(chat); } + + @override + Map toJson() { + final json = _$AdminMessageToJson(this); + json['message_type'] = MessageType.admin.name; // Check + return json; + } } diff --git a/lib/src/public/core/message/admin_message.g.dart b/lib/src/public/core/message/admin_message.g.dart index d895cb50..ad685eb2 100644 --- a/lib/src/public/core/message/admin_message.g.dart +++ b/lib/src/public/core/message/admin_message.g.dart @@ -46,15 +46,47 @@ AdminMessage _$AdminMessageFromJson(Map json) => AdminMessage( [], extendedMessage: json['extended_message'] as Map? ?? {}, ) - ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false ..allMetaArrays = (json['sorted_metaarray'] as List?) ?.map((e) => MessageMetaArray.fromJson(e as Map)) .toList() + ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false ..errorCode = json['error_code'] as int? ..sender = json['user'] == null ? null : Sender.fromJson(json['user'] as Map); +Map _$AdminMessageToJson(AdminMessage instance) => + { + 'channel_url': instance.channelUrl, + 'channel_type': _$ChannelTypeEnumMap[instance.channelType]!, + 'data': instance.data, + 'custom_type': instance.customType, + 'mention_type': _$MentionTypeEnumMap[instance.mentionType], + 'mentioned_users': + instance.mentionedUsers.map((e) => e.toJson()).toList(), + 'sorted_metaarray': + instance.allMetaArrays?.map((e) => e.toJson()).toList(), + 'extended_message': instance.extendedMessage, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + 'request_id': instance.requestId, + 'message_id': instance.messageId, + 'message': instance.message, + 'sending_status': _$SendingStatusEnumMap[instance.sendingStatus], + 'is_reply_to_channel': instance.isReplyToChannel, + 'parent_message_id': instance.parentMessageId, + 'parent_message_info': instance.parentMessage?.toJson(), + 'thread_info': instance.threadInfo?.toJson(), + 'message_survival_seconds': instance.messageSurvivalSeconds, + 'silent': instance.isSilent, + 'error_code': instance.errorCode, + 'is_op_msg': instance.isOperatorMessage, + 'og_tag': instance.ogMetaData?.toJson(), + 'reactions': instance.reactions?.map((e) => e.toJson()).toList(), + 'force_update_last_message': instance.forceUpdateLastMessage, + 'user': instance.sender?.toJson(), + }; + const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', diff --git a/lib/src/public/core/message/base_message.dart b/lib/src/public/core/message/base_message.dart index faf04802..9796943e 100644 --- a/lib/src/public/core/message/base_message.dart +++ b/lib/src/public/core/message/base_message.dart @@ -1,21 +1,20 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'dart:convert'; +import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; -import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; -import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/scheduled_message/group_channel_scheduled_message_get_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/message/channel_message_get_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command_type.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/admin_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/file_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/user_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; @@ -27,7 +26,6 @@ import 'package:sendbird_chat_sdk/src/public/main/define/sendbird_error.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/channel_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/info/scheduled_info.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/message/message_meta_array.dart'; -import 'package:sendbird_chat_sdk/src/public/main/model/message/notification_data.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/og/og_meta_data.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction_event.dart'; @@ -41,7 +39,7 @@ import 'package:sendbird_chat_sdk/src/public/main/params/message/threaded_messag part 'package:sendbird_chat_sdk/src/internal/main/extensions/base_message_extensions.dart'; /// Base class for messages. -abstract class BaseMessage { +abstract class BaseMessage extends RootMessage { /// The request ID of the message. final String? requestId; @@ -55,23 +53,6 @@ abstract class BaseMessage { /// The sending status of the message. SendingStatus? sendingStatus; - /// The channel URL of the channel this message belongs to. - final String channelUrl; - - /// The [ChannelType] of the channel this message belongs to. - @JsonKey(defaultValue: ChannelType.group, unknownEnumValue: ChannelType.group) - ChannelType channelType; - - /// The mention type. Refer to [MentionType]. - @JsonKey(unknownEnumValue: null) - final MentionType? mentionType; - - /// The creation time of the message in milliseconds. - final int createdAt; - - /// The updated time of the message in milliseconds. - final int updatedAt; - /// Determines whether the current message is a replied message and also a message was replied to the channel. @JsonKey(name: 'is_reply_to_channel', defaultValue: false) bool isReplyToChannel; @@ -97,13 +78,6 @@ abstract class BaseMessage { /// The thread info of the message. ThreadInfo? threadInfo; - /// All [MessageMetaArray]s of the message. - @JsonKey(name: 'sorted_metaarray') - List? allMetaArrays; - - /// The custom type of the message. - final String? customType; - /// The message's survival seconds. @JsonKey(defaultValue: -1) final int? messageSurvivalSeconds; @@ -124,10 +98,6 @@ abstract class BaseMessage { @JsonKey(name: 'is_op_msg') final bool isOperatorMessage; - /// The custom data of the message. - @JsonKey(fromJson: TypeChecker.fromJsonToNullableString) - String? data; - /// The [OGMetaData] of the message. (https://ogp.me/) /// Might be null if /// 1. Application does not support OG-TAG. (all new applications support OG-TAG by default) @@ -144,63 +114,46 @@ abstract class BaseMessage { @JsonKey(includeFromJson: false, includeToJson: false) ScheduledInfo? scheduledInfo; - /// [extendedMessage] is used for Sendbird UiKit. - /// Only featured in [GroupChannel] - @JsonKey(name: "extended_message", defaultValue: {}) - Map extendedMessage; - - /// notificationData - /// @since 4.0.7 - @JsonKey(includeFromJson: false, includeToJson: false) - NotificationData? notificationData; - final bool forceUpdateLastMessage; @JsonKey(includeFromJson: false, includeToJson: false) Sender? _sender; - @JsonKey(defaultValue: [], name: 'mentioned_users') - List _mentionedUsers; - - @JsonKey(includeFromJson: false, includeToJson: false) - late Chat chat; - BaseMessage({ + required super.channelUrl, + required super.channelType, required this.message, required this.sendingStatus, - required this.channelUrl, - required this.channelType, + super.data, + super.customType, + super.mentionType, + super.mentionedUsers, + super.allMetaArrays, + super.extendedMessage, + super.createdAt, + super.updatedAt, Sender? sender, - List mentionedUsers = const [], this.requestId, this.messageId = 0, - this.mentionType, this.isReplyToChannel = false, - this.createdAt = 0, - this.updatedAt = 0, this.parentMessageId, Map? parentMessage, this.threadInfo, - this.allMetaArrays, - this.customType, this.messageSurvivalSeconds, this.forceUpdateLastMessage = false, this.isSilent = false, this.errorCode, this.isOperatorMessage = false, - this.data, this.ogMetaData, this.reactions = const [], this.scheduledInfo, - this.extendedMessage = const {}, - }) : _sender = sender, - _mentionedUsers = mentionedUsers { + }) : _sender = sender { if (parentMessage != null && parentMessageId != null) { parentMessage['message_id'] = parentMessageId; parentMessage['channel_url'] = channelUrl; parentMessage['channel_type'] = channelType; - this.parentMessage = BaseMessage.fromJson(parentMessage); + this.parentMessage = RootMessage.fromJson(parentMessage) as BaseMessage; } if (sendingStatus == null) { @@ -214,15 +167,19 @@ abstract class BaseMessage { } } + MessageType get messageType => this is UserMessage + ? MessageType.user + : this is FileMessage + ? MessageType.file + : MessageType.admin; + + @override void set(Chat chat) { - this.chat = chat; + super.set(chat); + parentMessage?.set(chat); _sender?.set(chat); - for (final element in _mentionedUsers) { - element.set(chat); - } - final users = threadInfo?.mostRepliesUsers; if (users != null) { for (final element in users) { @@ -256,27 +213,6 @@ abstract class BaseMessage { return _sender; } - set mentionedUsers(value) => _mentionedUsers = value; - - /// The mentioned users of the message. - List get mentionedUsers { - if (chat.chatContext.options.useMemberInfoInMessage) { - final channel = - chat.channelCache.find(channelKey: channelUrl); - if (channel is GroupChannel) { - for (final mentionedUser in _mentionedUsers) { - final member = channel.getMember(mentionedUser.userId); - if (member != null) { - mentionedUser.nickname = member.nickname; - mentionedUser.profileUrl = member.profileUrl; - mentionedUser.metaData = member.metaData; - } - } - } - } - return _mentionedUsers; - } - /// Whether the message is resendable. bool isResendable() { bool result = false; @@ -392,7 +328,7 @@ abstract class BaseMessage { parentMessageId: messageId, ), ); - final messages = res.messages; + final messages = List.from(res.messages); return ThreadedMessages( parentMessage: messages.first, @@ -440,6 +376,7 @@ abstract class BaseMessage { @override bool operator ==(other) { if (identical(other, this)) return true; + if (!(super == (other))) return false; final eq = const ListEquality().equals; return other is BaseMessage && @@ -447,14 +384,6 @@ abstract class BaseMessage { other.message == message && other.sendingStatus == sendingStatus && other._sender == _sender && - other.channelUrl == channelUrl && - other.channelType == channelType && - other.mentionType == mentionType && - eq(other._mentionedUsers, _mentionedUsers) && - other.createdAt == createdAt && - other.updatedAt == updatedAt && - other.customType == customType && - other.data == data && other.isSilent == isSilent && other.threadInfo == threadInfo && eq(other.reactions, reactions); @@ -462,148 +391,30 @@ abstract class BaseMessage { @override int get hashCode => Object.hash( + super.hashCode, messageId, message, sendingStatus, _sender, - channelUrl, - channelType, - _mentionedUsers, - mentionType, - createdAt, - updatedAt, isSilent, - customType, - data, parentMessageId, threadInfo, - allMetaArrays, reactions, ); - static T getMessageFromJsonWithChat( - Chat chat, - Map json, { - ChannelType? channelType, - String? commandType, - }) { - return _fromJson( - json, - chat: chat, - channelType: channelType, - commandType: commandType, - ); - } - - factory BaseMessage.fromJson(Map json) { - return _fromJson(json); + Uint8List serialize() { + return Uint8List.fromList(jsonEncode(toJson()).codeUnits); } - factory BaseMessage.fromJsonWithChat(Chat chat, Map json) { - return BaseMessage.fromJson(json)..set(chat); - } - - static T _fromJson( - Map json, { - Chat? chat, - ChannelType? channelType, - String? commandType, - }) { - // BaseMessage backward compatibility - if (json['custom'] != null) json['data'] = json['custom']; - if (json['ts'] != null) json['created_at'] = json['ts']; - if (json['msg_id'] != null) json['message_id'] = json['msg_id']; - if (json['req_id'] != null) json['request_id'] = json['req_id']; - - // Insert type if channel is provided manually - if (channelType != null) { - json['channel_type'] = channelType.asString(); - } - - // Insert reply_to_channel manually - if (json['reply_to_channel'] != null) { - json['is_reply_to_channel'] = json['reply_to_channel']; - } - - String? type = commandType; - if (type == null) { - final Map? file = json['file']; - if (file != null && file.isNotEmpty) { - // The 'type' value of FileMessage payload can be 'FILE' or 'image/jpeg'. - type = CommandType.fileMessage.value; - } else { - type = json['type'] as String; - } - } - - // final type = commandType ?? json['type'] as String; - - BaseMessage message; - if (chat != null) { - if (T == UserMessage || CommandString.isUserMessage(type)) { - message = UserMessage.fromJsonWithChat(chat, json) as T; - } else if (T == FileMessage || CommandString.isFileMessage(type)) { - message = FileMessage.fromJsonWithChat(chat, json) as T; - } else if (T == AdminMessage || CommandString.isAdminMessage(type)) { - message = AdminMessage.fromJsonWithChat(chat, json) as T; - } else { - throw InvalidMessageTypeException(); - } - } else { - if (CommandString.isUserMessage(type)) { - message = UserMessage.fromJson(json); - } else if (CommandString.isFileMessage(type)) { - message = FileMessage.fromJson(json); - } else if (CommandString.isAdminMessage(type)) { - message = AdminMessage.fromJson(json); - } else { - throw InvalidMessageTypeException(); - } - } - - final metaArray = json['metaarray']; - final metaArrayKeys = List.from(json['metaarray_key_order'] ?? []); - - // NOTE: sorted_metaarray is from API, metaarray list is local, - // metaarray map is from Web, so had to handle separately. - if (metaArray is List) { - // Local cmd case - message.allMetaArrays = metaArray - .map((e) => MessageMetaArray.fromJson(e as Map)) - .toList(); - } else if (metaArray is Map && metaArrayKeys.isNotEmpty) { - // WebSocket cmd result case - message.allMetaArrays = metaArrayKeys.map((e) { - final value = List.from(metaArray[e]); - return MessageMetaArray(key: e, value: value); - }).toList(); + static BaseMessage? buildFromSerializedData(Uint8List data) { + final json = jsonDecode(String.fromCharCodes(data)); + if (json['message_type'] == MessageType.user.name) { + return UserMessage.fromJson(jsonDecode(String.fromCharCodes(data))); + } else if (json['message_type'] == MessageType.file.name) { + return FileMessage.fromJson(jsonDecode(String.fromCharCodes(data))); + } else if (json['message_type'] == MessageType.admin.name) { + return AdminMessage.fromJson(jsonDecode(String.fromCharCodes(data))); } - - // notificationData - if (message.extendedMessage.isNotEmpty) { - if (message.extendedMessage['sub_type'] != null && - (message.extendedMessage['sub_type'] as int) == 0) { - final subData = message.extendedMessage['sub_data']; - if (subData != null) { - Map? subDataMap = jsonDecode(subData); - if (subDataMap != null) { - final List? tags = (subDataMap['tags'] as List?) - ?.map((value) => value as String) - .toList(); - - message.notificationData = NotificationData( - templateKey: subDataMap['template_key'] as String? ?? '', - templateVariables: - subDataMap['template_variables'] as Map? ?? - {}, - label: subDataMap['label'] as String?, - tags: tags, - ); - } - } - } - } - - return message as T; + return null; } } diff --git a/lib/src/public/core/message/file_message.dart b/lib/src/public/core/message/file_message.dart index 1463ef60..84e17faf 100644 --- a/lib/src/public/core/message/file_message.dart +++ b/lib/src/public/core/message/file_message.dart @@ -6,6 +6,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; @@ -22,7 +23,7 @@ import 'package:uuid/uuid.dart'; part 'file_message.g.dart'; /// Object representing a file. -@JsonSerializable(createToJson: false) +@JsonSerializable() class FileMessage extends BaseMessage { /// The file URL. final String url; @@ -146,6 +147,13 @@ class FileMessage extends BaseMessage { return FileMessage.fromJson(json)..set(chat); } + @override + Map toJson() { + final json = _$FileMessageToJson(this); + json['message_type'] = MessageType.file.name; // Check + return json; + } + factory FileMessage.fromParams({ required FileMessageCreateParams params, required BaseChannel channel, @@ -202,7 +210,7 @@ class FileMessage extends BaseMessage { } /// An object represents thumbnail -@JsonSerializable(createToJson: false) +@JsonSerializable() class Thumbnail { String url; String? plainUrl; @@ -223,6 +231,8 @@ class Thumbnail { factory Thumbnail.fromJson(Map json) => _$ThumbnailFromJson(json); + Map toJson() => _$ThumbnailToJson(this); + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/core/message/file_message.g.dart b/lib/src/public/core/message/file_message.g.dart index 1be1da28..f2490a2c 100644 --- a/lib/src/public/core/message/file_message.g.dart +++ b/lib/src/public/core/message/file_message.g.dart @@ -56,13 +56,51 @@ FileMessage _$FileMessageFromJson(Map json) => FileMessage( [], parentMessage: json['parent_message_info'] as Map?, ) - ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false ..allMetaArrays = (json['sorted_metaarray'] as List?) ?.map((e) => MessageMetaArray.fromJson(e as Map)) .toList() - ..errorCode = json['error_code'] as int? ..extendedMessage = - json['extended_message'] as Map? ?? {}; + json['extended_message'] as Map? ?? {} + ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false + ..errorCode = json['error_code'] as int?; + +Map _$FileMessageToJson(FileMessage instance) => + { + 'channel_url': instance.channelUrl, + 'channel_type': _$ChannelTypeEnumMap[instance.channelType]!, + 'data': instance.data, + 'custom_type': instance.customType, + 'mention_type': _$MentionTypeEnumMap[instance.mentionType], + 'mentioned_users': + instance.mentionedUsers.map((e) => e.toJson()).toList(), + 'sorted_metaarray': + instance.allMetaArrays?.map((e) => e.toJson()).toList(), + 'extended_message': instance.extendedMessage, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + 'request_id': instance.requestId, + 'message_id': instance.messageId, + 'message': instance.message, + 'sending_status': _$SendingStatusEnumMap[instance.sendingStatus], + 'is_reply_to_channel': instance.isReplyToChannel, + 'parent_message_id': instance.parentMessageId, + 'parent_message_info': instance.parentMessage?.toJson(), + 'thread_info': instance.threadInfo?.toJson(), + 'message_survival_seconds': instance.messageSurvivalSeconds, + 'silent': instance.isSilent, + 'error_code': instance.errorCode, + 'is_op_msg': instance.isOperatorMessage, + 'og_tag': instance.ogMetaData?.toJson(), + 'reactions': instance.reactions?.map((e) => e.toJson()).toList(), + 'force_update_last_message': instance.forceUpdateLastMessage, + 'user': instance.sender?.toJson(), + 'url': instance.url, + 'name': instance.name, + 'size': instance.size, + 'type': instance.type, + 'thumbnails': instance.thumbnails?.map((e) => e.toJson()).toList(), + 'require_auth': instance.requireAuth, + }; const _$SendingStatusEnumMap = { SendingStatus.none: 'none', @@ -91,3 +129,12 @@ Thumbnail _$ThumbnailFromJson(Map json) => Thumbnail( (json['real_height'] as num?)?.toDouble(), (json['real_width'] as num?)?.toDouble(), ); + +Map _$ThumbnailToJson(Thumbnail instance) => { + 'url': instance.url, + 'plain_url': instance.plainUrl, + 'height': instance.height, + 'width': instance.width, + 'real_height': instance.realHeight, + 'real_width': instance.realWidth, + }; diff --git a/lib/src/public/core/message/notification_message.dart b/lib/src/public/core/message/notification_message.dart new file mode 100644 index 00000000..d7f57f76 --- /dev/null +++ b/lib/src/public/core/message/notification_message.dart @@ -0,0 +1,107 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'dart:convert'; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/message_meta_array.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/notification_data.dart'; + +part 'notification_message.g.dart'; + +/// Class representing a notification message. +/// @since 4.1.0 +@JsonSerializable() +class NotificationMessage extends RootMessage { + /// notificationId + @JsonKey(name: 'notification_message_id') + final String notificationId; + + /// notificationMessageStatus + @JsonKey( + fromJson: _messageStatusValueOf, + name: 'message_status', + defaultValue: NotificationMessageStatus.sent, + ) + NotificationMessageStatus messageStatus; + + /// notificationData + @JsonKey(includeFromJson: false, includeToJson: false) + NotificationData? notificationData; + + NotificationMessage({ + required super.channelUrl, + required super.channelType, + required this.notificationId, + required this.messageStatus, + super.data, + super.customType, + super.mentionType, + super.mentionedUsers, + super.allMetaArrays, + super.extendedMessage, + super.createdAt, + super.updatedAt, + }); + + factory NotificationMessage.fromJson(Map json) { + return NotificationMessage.fromJsonWithChat(SendbirdChat().chat, json); + } + + factory NotificationMessage.fromJsonWithChat( + Chat? chat, + Map json, + ) { + final message = _$NotificationMessageFromJson(json); + + // notificationData + if (message.extendedMessage.isNotEmpty) { + if (message.extendedMessage['sub_type'] != null && + (message.extendedMessage['sub_type'] as int) == 0) { + final subData = message.extendedMessage['sub_data']; + if (subData != null) { + Map? subDataMap = jsonDecode(subData); + if (subDataMap != null) { + final List? tags = (subDataMap['tags'] as List?) + ?.map((value) => value as String) + .toList(); + + message.notificationData = NotificationData( + templateKey: subDataMap['template_key'] as String? ?? '', + templateVariables: + subDataMap['template_variables'] as Map? ?? + {}, + label: subDataMap['label'] as String?, + tags: tags, + ); + } + } + } + } + return message..set(chat ?? SendbirdChat().chat); // Set the singleton chat + } + + @override + Map toJson() { + final json = _$NotificationMessageToJson(this); + json['message_type'] = MessageType.admin.name; // Check + // Check: notificationData + return json; + } + + static NotificationMessageStatus _messageStatusValueOf(String value) { + switch (value) { + case 'SENT': + return NotificationMessageStatus.sent; + case 'READ': + return NotificationMessageStatus.read; + default: + return NotificationMessageStatus.sent; + } + } +} diff --git a/lib/src/public/core/message/notification_message.g.dart b/lib/src/public/core/message/notification_message.g.dart new file mode 100644 index 00000000..93b1808b --- /dev/null +++ b/lib/src/public/core/message/notification_message.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification_message.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NotificationMessage _$NotificationMessageFromJson(Map json) => + NotificationMessage( + channelUrl: json['channel_url'] as String, + channelType: $enumDecodeNullable( + _$ChannelTypeEnumMap, json['channel_type'], + unknownValue: ChannelType.group) ?? + ChannelType.group, + notificationId: json['notification_message_id'] as String, + messageStatus: json['message_status'] == null + ? NotificationMessageStatus.sent + : NotificationMessage._messageStatusValueOf( + json['message_status'] as String), + data: TypeChecker.fromJsonToNullableString(json['data']), + customType: json['custom_type'] as String?, + mentionType: + $enumDecodeNullable(_$MentionTypeEnumMap, json['mention_type']), + mentionedUsers: (json['mentioned_users'] as List?) + ?.map((e) => User.fromJson(e as Map)) + .toList() ?? + const [], + allMetaArrays: (json['sorted_metaarray'] as List?) + ?.map((e) => MessageMetaArray.fromJson(e as Map)) + .toList(), + extendedMessage: json['extended_message'] as Map? ?? {}, + createdAt: json['created_at'] as int? ?? 0, + updatedAt: json['updated_at'] as int? ?? 0, + ); + +Map _$NotificationMessageToJson( + NotificationMessage instance) => + { + 'channel_url': instance.channelUrl, + 'channel_type': _$ChannelTypeEnumMap[instance.channelType]!, + 'data': instance.data, + 'custom_type': instance.customType, + 'mention_type': _$MentionTypeEnumMap[instance.mentionType], + 'mentioned_users': + instance.mentionedUsers.map((e) => e.toJson()).toList(), + 'sorted_metaarray': + instance.allMetaArrays?.map((e) => e.toJson()).toList(), + 'extended_message': instance.extendedMessage, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + 'notification_message_id': instance.notificationId, + 'message_status': + _$NotificationMessageStatusEnumMap[instance.messageStatus]!, + }; + +const _$ChannelTypeEnumMap = { + ChannelType.group: 'group', + ChannelType.open: 'open', + ChannelType.feed: 'feed', +}; + +const _$MentionTypeEnumMap = { + MentionType.users: 'users', + MentionType.channel: 'channel', +}; + +const _$NotificationMessageStatusEnumMap = { + NotificationMessageStatus.sent: 'sent', + NotificationMessageStatus.read: 'read', +}; diff --git a/lib/src/public/core/message/root_message.dart b/lib/src/public/core/message/root_message.dart new file mode 100644 index 00000000..b78421b8 --- /dev/null +++ b/lib/src/public/core/message/root_message.dart @@ -0,0 +1,263 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:collection/collection.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/extensions/extensions.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command_type.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/admin_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/file_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/user_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/message_meta_array.dart'; + +/// Class representing a root message. +abstract class RootMessage { + /// The channel URL of the channel this message belongs to. + final String channelUrl; + + /// The [ChannelType] of the channel this message belongs to. + @JsonKey(defaultValue: ChannelType.group, unknownEnumValue: ChannelType.group) + ChannelType channelType; + + /// The custom data of the message. + @JsonKey(fromJson: TypeChecker.fromJsonToNullableString) + String? data; + + /// The custom type of the message. + final String? customType; + + /// The mention type. Refer to [MentionType]. + @JsonKey(unknownEnumValue: null) + final MentionType? mentionType; + + @JsonKey(name: 'mentioned_users', defaultValue: []) + List _mentionedUsers; + + /// The mentioned users of the message. + List get mentionedUsers { + if (chat.chatContext.options.useMemberInfoInMessage) { + final channel = + chat.channelCache.find(channelKey: channelUrl); + if (channel is GroupChannel) { + for (final mentionedUser in _mentionedUsers) { + final member = channel.getMember(mentionedUser.userId); + if (member != null) { + mentionedUser.nickname = member.nickname; + mentionedUser.profileUrl = member.profileUrl; + mentionedUser.metaData = member.metaData; + } + } + } + } + return _mentionedUsers; + } + + /// All [MessageMetaArray]s of the message. + @JsonKey(name: 'sorted_metaarray') + List? allMetaArrays; + + /// [extendedMessage] is used for Sendbird UiKit. + /// Only featured in [GroupChannel] + @JsonKey(name: "extended_message", defaultValue: {}) + Map extendedMessage; + + /// The creation time of the message in milliseconds. + final int createdAt; + + /// The updated time of the message in milliseconds. + final int updatedAt; + + @JsonKey(includeFromJson: false, includeToJson: false) + late Chat chat; + + RootMessage({ + required this.channelUrl, + required this.channelType, + this.data, + this.customType, + this.mentionType, + List mentionedUsers = const [], + this.allMetaArrays, + Map? extendedMessage, + this.createdAt = 0, + this.updatedAt = 0, + }) : _mentionedUsers = mentionedUsers, + extendedMessage = extendedMessage ?? {}; + + void set(Chat chat) { + this.chat = chat; + + for (final element in _mentionedUsers) { + element.set(chat); + } + } + + set mentionedUsers(value) => _mentionedUsers = value; + + dynamic getMessageId() { + if (this is NotificationMessage) { + return (this as NotificationMessage).notificationId; + } + + // BaseMessage + return (this as BaseMessage).messageId; + } + + Map toJson(); + + static RootMessage getMessageFromJsonWithChat( + Chat chat, + Map json, { + ChannelType? channelType, + String? commandType, + }) { + return _fromJson( + json, + chat: chat, + channelType: channelType, + commandType: commandType, + ); + } + + static RootMessage fromJson(Map json) { + return _fromJson(json); + } + + static RootMessage fromJsonWithChat(Chat chat, Map json) { + return RootMessage.fromJson(json)..set(chat); + } + + static RootMessage _fromJson( + Map json, { + Chat? chat, + ChannelType? channelType, + String? commandType, + }) { + // BaseMessage backward compatibility + if (json['custom'] != null) json['data'] = json['custom']; + if (json['ts'] != null) json['created_at'] = json['ts']; + if (json['msg_id'] != null) json['message_id'] = json['msg_id']; + if (json['req_id'] != null) json['request_id'] = json['req_id']; + + // Insert type if channel is provided manually + if (channelType != null) { + json['channel_type'] = channelType.asString(); + } + + // NotificationMessage + if (json['notification_message_id'] != null && + (json['notification_message_id'] as String).isNotEmpty) { + return NotificationMessage.fromJsonWithChat(chat, json); + } + + // Insert reply_to_channel manually + if (json['reply_to_channel'] != null) { + json['is_reply_to_channel'] = json['reply_to_channel']; + } + + String? type = commandType; + if (type == null) { + final Map? file = json['file']; + if (file != null && file.isNotEmpty) { + // The 'type' value of FileMessage payload can be 'FILE' or 'image/jpeg'. + type = CommandType.fileMessage.value; + } else { + type = json['type'] as String; + } + } + + // final type = commandType ?? json['type'] as String; + + BaseMessage message; + if (chat != null) { + if (T == UserMessage || CommandString.isUserMessage(type)) { + message = UserMessage.fromJsonWithChat(chat, json) as T; + } else if (T == FileMessage || CommandString.isFileMessage(type)) { + message = FileMessage.fromJsonWithChat(chat, json) as T; + } else if (T == AdminMessage || CommandString.isAdminMessage(type)) { + message = AdminMessage.fromJsonWithChat(chat, json) as T; + } else { + throw InvalidMessageTypeException(); + } + } else { + if (CommandString.isUserMessage(type)) { + message = UserMessage.fromJson(json); + } else if (CommandString.isFileMessage(type)) { + message = FileMessage.fromJson(json); + } else if (CommandString.isAdminMessage(type)) { + message = AdminMessage.fromJson(json); + } else { + throw InvalidMessageTypeException(); + } + } + + final metaArray = json['metaarray']; + final metaArrayKeys = List.from(json['metaarray_key_order'] ?? []); + + // NOTE: sorted_metaarray is from API, metaarray list is local, + // metaarray map is from Web, so had to handle separately. + if (metaArray is List) { + // Local cmd case + message.allMetaArrays = metaArray + .map((e) => MessageMetaArray.fromJson(e as Map)) + .toList(); + } else if (metaArray is Map && metaArrayKeys.isNotEmpty) { + // WebSocket cmd result case + message.allMetaArrays = metaArrayKeys.map((e) { + final value = List.from(metaArray[e]); + return MessageMetaArray(key: e, value: value); + }).toList(); + } + + return message as T; + } + + @override + bool operator ==(other) { + if (identical(other, this)) return true; + + final eq = const ListEquality().equals; + final mapEq = const MapEquality().equals; + return other is RootMessage && + other.channelUrl == channelUrl && + other.channelType == channelType && + other.data == data && + other.customType == customType && + other.mentionType == mentionType && + eq(other._mentionedUsers, _mentionedUsers) && + eq(other.allMetaArrays, allMetaArrays) && + mapEq(other.extendedMessage, extendedMessage) && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode => Object.hash( + channelUrl, + channelType, + data, + customType, + channelUrl, + channelType, + mentionType, + _mentionedUsers, + allMetaArrays, + extendedMessage, + createdAt, + updatedAt, + ); +} + +enum MessageType { + user, + file, + admin, +} diff --git a/lib/src/public/core/message/user_message.dart b/lib/src/public/core/message/user_message.dart index da60e3a9..45c11a28 100644 --- a/lib/src/public/core/message/user_message.dart +++ b/lib/src/public/core/message/user_message.dart @@ -6,6 +6,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/type_checker.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/sender.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; @@ -21,7 +22,7 @@ import 'package:sendbird_chat_sdk/src/public/main/params/message/user_message_cr part 'user_message.g.dart'; /// Object representing a user message. -@JsonSerializable(createToJson: false) +@JsonSerializable() class UserMessage extends BaseMessage { /// The translated messages (key-value map) for the language codes in key. /// The messages that have been sent with translation option will have this map. @@ -110,6 +111,13 @@ class UserMessage extends BaseMessage { return UserMessage.fromJson(json)..set(chat); } + @override + Map toJson() { + final json = _$UserMessageToJson(this); + json['message_type'] = MessageType.user.name; // Check + return json; + } + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/core/message/user_message.g.dart b/lib/src/public/core/message/user_message.g.dart index c129bc58..bb334e9e 100644 --- a/lib/src/public/core/message/user_message.g.dart +++ b/lib/src/public/core/message/user_message.g.dart @@ -59,13 +59,48 @@ UserMessage _$UserMessageFromJson(Map json) => UserMessage( ?.map((e) => e as String) .toList(), ) - ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false ..allMetaArrays = (json['sorted_metaarray'] as List?) ?.map((e) => MessageMetaArray.fromJson(e as Map)) .toList() - ..errorCode = json['error_code'] as int? ..extendedMessage = - json['extended_message'] as Map? ?? {}; + json['extended_message'] as Map? ?? {} + ..isReplyToChannel = json['is_reply_to_channel'] as bool? ?? false + ..errorCode = json['error_code'] as int?; + +Map _$UserMessageToJson(UserMessage instance) => + { + 'channel_url': instance.channelUrl, + 'channel_type': _$ChannelTypeEnumMap[instance.channelType]!, + 'data': instance.data, + 'custom_type': instance.customType, + 'mention_type': _$MentionTypeEnumMap[instance.mentionType], + 'mentioned_users': + instance.mentionedUsers.map((e) => e.toJson()).toList(), + 'sorted_metaarray': + instance.allMetaArrays?.map((e) => e.toJson()).toList(), + 'extended_message': instance.extendedMessage, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + 'request_id': instance.requestId, + 'message_id': instance.messageId, + 'message': instance.message, + 'sending_status': _$SendingStatusEnumMap[instance.sendingStatus], + 'is_reply_to_channel': instance.isReplyToChannel, + 'parent_message_id': instance.parentMessageId, + 'parent_message_info': instance.parentMessage?.toJson(), + 'thread_info': instance.threadInfo?.toJson(), + 'message_survival_seconds': instance.messageSurvivalSeconds, + 'silent': instance.isSilent, + 'error_code': instance.errorCode, + 'is_op_msg': instance.isOperatorMessage, + 'og_tag': instance.ogMetaData?.toJson(), + 'reactions': instance.reactions?.map((e) => e.toJson()).toList(), + 'force_update_last_message': instance.forceUpdateLastMessage, + 'user': instance.sender?.toJson(), + 'translations': instance.translations, + 'translation_target_languages': instance.translationTargetLanguages, + 'poll': instance.poll?.toJson(), + }; const _$ChannelTypeEnumMap = { ChannelType.group: 'group', diff --git a/lib/src/public/core/user/member.dart b/lib/src/public/core/user/member.dart index 8187dfe7..83f8dae1 100644 --- a/lib/src/public/core/user/member.dart +++ b/lib/src/public/core/user/member.dart @@ -11,7 +11,7 @@ import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; part 'member.g.dart'; /// Represents a `GroupChannel` member. -@JsonSerializable(createToJson: false) +@JsonSerializable() class Member extends User { /// The [Member]'s invitation state. @JsonKey(name: 'state', unknownEnumValue: MemberState.none) @@ -75,6 +75,9 @@ class Member extends User { return Member.fromJson(json)..set(chat); } + @override + Map toJson() => _$MemberToJson(this); + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/core/user/member.g.dart b/lib/src/public/core/user/member.g.dart index e66b95e8..fc5cf725 100644 --- a/lib/src/public/core/user/member.g.dart +++ b/lib/src/public/core/user/member.g.dart @@ -35,6 +35,25 @@ Member _$MemberFromJson(Map json) => Member( requireAuth: json['require_auth_for_profile_image'] as bool? ?? false, )..isActive = json['is_active'] as bool?; +Map _$MemberToJson(Member instance) => { + 'user_id': instance.userId, + 'nickname': instance.nickname, + 'profile_url': instance.profileUrl, + 'is_online': connectionStatusToBool(instance.connectionStatus), + 'last_seen_at': instance.lastSeenAt, + 'is_active': instance.isActive, + 'preferred_languages': instance.preferredLanguages, + 'friend_discovery_key': instance.friendDiscoveryKey, + 'friend_name': instance.friendName, + 'metadata': instance.metaData, + 'require_auth_for_profile_image': instance.requireAuth, + 'state': _$MemberStateEnumMap[instance.memberState]!, + 'is_blocked_by_me': instance.isBlockedByMe, + 'is_blocking_me': instance.isBlockingMe, + 'is_muted': instance.isMuted, + 'role': _$RoleEnumMap[instance.role]!, + }; + const _$MemberStateEnumMap = { MemberState.none: 'none', MemberState.invited: 'invited', diff --git a/lib/src/public/main/chat/sendbird_chat.dart b/lib/src/public/main/chat/sendbird_chat.dart index d2492d2b..25752487 100644 --- a/lib/src/public/main/chat/sendbird_chat.dart +++ b/lib/src/public/main/chat/sendbird_chat.dart @@ -350,7 +350,7 @@ class SendbirdChat { // Event Handler //------------------------------// /// Adds a channel handler. All added handlers will be notified when events occur. - static void addChannelHandler(String identifier, BaseChannelHandler handler) { + static void addChannelHandler(String identifier, RootChannelHandler handler) { sbLog.i(StackTrace.current, 'identifier: $identifier'); _instance._chat.addChannelHandler(identifier, handler); } diff --git a/lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart b/lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart index ffd4cafb..d32bbd02 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart @@ -8,9 +8,9 @@ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/ /// The base message collection that handles message lists. /// @since 4.0.3 -class BaseMessageCollection { +abstract class BaseMessageCollection { /// The list of succeeded message list in this collection. - final List messageList = []; + List get messageList; /// The starting point of the collection. int get startingPoint => _startingPoint; @@ -45,7 +45,7 @@ class BaseMessageCollection { Chat get chat => _chat; - BaseMessage? get latestMessage => _latestMessage; + RootMessage? get latestMessage => _latestMessage; final BaseChannel _channel; final BaseMessageCollectionHandler _handler; @@ -60,8 +60,8 @@ class BaseMessageCollection { bool _isLoading = false; bool _hasPrevious = false; bool _hasNext = false; - BaseMessage? _oldestMessage; - BaseMessage? _latestMessage; + RootMessage? _oldestMessage; + RootMessage? _latestMessage; BaseMessageCollection({ required BaseChannel channel, @@ -282,7 +282,7 @@ class BaseMessageCollection { bool canAddMessage( CollectionEventSource eventSource, - BaseMessage addedMessage, + RootMessage addedMessage, ) { if (eventSource == CollectionEventSource.messageLoadPrevious || eventSource == CollectionEventSource.messageLoadNext) { @@ -304,11 +304,11 @@ class BaseMessageCollection { return true; } - int _getExistedMessageCountInMessageList(List loadedMessages) { + int _getExistedMessageCountInMessageList(List loadedMessages) { int existedMessageCount = 0; for (final loadedMessage in loadedMessages) { for (final message in messageList) { - if (loadedMessage.messageId == message.messageId) { + if (loadedMessage.getMessageId() == message.getMessageId()) { existedMessageCount++; break; } diff --git a/lib/src/public/main/collection/group_channel_message_collection/message_collection.dart b/lib/src/public/main/collection/group_channel_message_collection/message_collection.dart index 0872ff19..495d72cf 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/message_collection.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/message_collection.dart @@ -7,6 +7,10 @@ import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart' /// Message collection that handles message lists. class MessageCollection extends BaseMessageCollection { + /// The list of succeeded message list in this collection. + @override + final List messageList = []; + /// The [GroupChannel] tracked by this [MessageCollection]. GroupChannel get channel => baseChannel as GroupChannel; diff --git a/lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart b/lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart index 2056d053..7342bb13 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart @@ -8,6 +8,11 @@ import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart' /// Notification collection that handles message lists. /// @since 4.0.3 class NotificationCollection extends BaseMessageCollection { + /// The list of succeeded message list in this collection. + /// @since 4.1.0 + @override + final List messageList = []; + /// The [FeedChannel] tracked by this [NotificationCollection]. /// @since 4.0.3 FeedChannel get channel => baseChannel as FeedChannel; diff --git a/lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart b/lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart index 570a6117..e41c28b3 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart @@ -1,7 +1,7 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_channel.dart'; -import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_collection/feed_channel_context.dart'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/message_collection.dart'; @@ -10,50 +10,48 @@ import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_messa import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; /// A handler used in [NotificationCollection]. -/// @since 4.0.3 abstract class NotificationCollectionHandler extends BaseMessageCollectionHandler { - /// Called when one or more [BaseMessage] is added to this collection. + /// Called when one or more [NotificationMessage] is added to this collection. /// /// [context] is a context of where this addition happened. /// [channel] is a channel this collection holds. - /// [messages] are list of [BaseMessage] that have been added. All messages will have the same [SendingStatus]. - /// @since 4.0.3 + /// [messages] are list of [NotificationMessage] that have been added. All messages will have the same [SendingStatus]. + /// @since 4.1.0 void onMessagesAdded( NotificationContext context, FeedChannel channel, - List messages, + List messages, ); - /// Called when one or more [BaseMessage] is update in this collection. + /// Called when one or more [NotificationMessage] is update in this collection. /// /// [context] is a context of where this update happened. /// [channel] is a channel this collection holds. - /// [messages] are list of [BaseMessage] that have been updated. All messages will have the same [SendingStatus]. - /// @since 4.0.3 + /// [messages] are list of [NotificationMessage] that have been updated. All messages will have the same [SendingStatus]. + /// @since 4.1.0 void onMessagesUpdated( NotificationContext context, FeedChannel channel, - List messages, + List messages, ); - /// Called when one or more [BaseMessage] is deleted from this collection. + /// Called when one or more [NotificationMessage] is deleted from this collection. /// /// [context] is a context of where this deletion happened. /// [channel] is a channel this collection holds. - /// [messages] are list of [BaseMessage] that have been deleted. All messages will have the same [SendingStatus]. - /// @since 4.0.3 + /// [messages] are list of [NotificationMessage] that have been deleted. All messages will have the same [SendingStatus]. + /// @since 4.1.0 void onMessagesDeleted( NotificationContext context, FeedChannel channel, - List messages, + List messages, ); /// Called when there's a change in the channel this collection holds. /// /// [context] is a channel context of where the change in channel happened. /// [channel] is a channel this collection holds. - /// @since 4.0.3 void onChannelUpdated( FeedChannelContext context, FeedChannel channel, @@ -63,7 +61,6 @@ abstract class NotificationCollectionHandler /// /// [context] is a channel context of where the change in channel happened. /// [deletedChannelUrl] isUrl a channel URL of this channel which has been deleted. - /// @since 4.0.3 void onChannelDeleted( FeedChannelContext context, String deletedChannelUrl, @@ -71,6 +68,5 @@ abstract class NotificationCollectionHandler /// Called when the collection has detected a huge gap between current message list. /// When this called, you should call [MessageCollection.dispose] to remove current collection and create a new collection. - /// @since 4.0.3 void onHugeGapDetected(); } diff --git a/lib/src/public/main/define/enums.dart b/lib/src/public/main/define/enums.dart index 448f11e8..0f1db9ef 100644 --- a/lib/src/public/main/define/enums.dart +++ b/lib/src/public/main/define/enums.dart @@ -332,3 +332,10 @@ enum LogLevel { warning, info, } + +/// NotificationMessageStatus +/// @since 4.1.0 +enum NotificationMessageStatus { + sent, + read, +} diff --git a/lib/src/public/main/handler/channel_handler.dart b/lib/src/public/main/handler/channel_handler.dart index aeca3700..153157c0 100644 --- a/lib/src/public/main/handler/channel_handler.dart +++ b/lib/src/public/main/handler/channel_handler.dart @@ -2,11 +2,13 @@ import 'package:sendbird_chat_sdk/sendbird_chat_sdk.dart'; +abstract class RootChannelHandler {} + /// The BaseChannel handler. /// This handler provides callbacks for events related [OpenChannel] or [GroupChannel]. /// All callbacks are called only when the currently logged-in [User] is a participant or member of `OpenChannel` or `GroupChannel` respectively. /// To add or remove this handler, refer to [SendbirdChat.addChannelHandler] and [SendbirdChat.removeChannelHandler]. -abstract class BaseChannelHandler { +abstract class BaseChannelHandler extends RootChannelHandler { /// A callback for when a message is received. void onMessageReceived(BaseChannel channel, BaseMessage message); @@ -91,7 +93,7 @@ abstract class BaseChannelHandler { /// The GroupChannel handler. abstract class GroupChannelHandler extends BaseChannelHandler { - /// A callback for when read receipts are updated on `GroupChannel`. + /// A callback for when read receipts are updated on [GroupChannel]. /// To use the updated read receipt, refer to [GroupChannelRead.getReadStatus], /// [GroupChannelRead.getReadMembers], [GroupChannelRead.getUnreadMembers]. void onReadStatusUpdated(GroupChannel channel) {} @@ -163,5 +165,13 @@ abstract class OpenChannelHandler extends BaseChannelHandler { } /// The FeedChannel handler. -/// @since 4.0.3 -abstract class FeedChannelHandler extends BaseChannelHandler {} +/// @since 4.1.0 +abstract class FeedChannelHandler extends RootChannelHandler { + /// A callback for when a message is received. + /// @since 4.1.0 + void onMessageReceived(FeedChannel channel, NotificationMessage message); + + /// A callback for when channel property is changed. + /// @since 4.1.0 + void onChannelChanged(FeedChannel channel) {} +} diff --git a/lib/src/public/main/handler/user_event_handler.dart b/lib/src/public/main/handler/user_event_handler.dart index 4b29e364..af5d12a1 100644 --- a/lib/src/public/main/handler/user_event_handler.dart +++ b/lib/src/public/main/handler/user_event_handler.dart @@ -13,14 +13,6 @@ abstract class UserEventHandler { /// The friends discovered event. void onFriendsDiscovered(List friends); - /// Gets the subscribed total number of unread message of all `GroupChannel`s the current user has joined, - /// and number of unread message of `GroupChannel` for all subscribed custom type. - @Deprecated('As of 4.0.3, replaced by [onTotalUnreadMessageCountChanged]') - void onTotalUnreadMessageCountUpdated( - int totalCount, - Map totalCountByCustomType, - ) {} - /// Gets the subscribed total number of unread message of all [GroupChannel]s and [FeedChannel]s the current user has joined, /// and number of unread message of [GroupChannel] for all subscribed custom type. /// @since 4.0.3 diff --git a/lib/src/public/main/model/channel/group_channel_filter.dart b/lib/src/public/main/model/channel/group_channel_filter.dart index fdf38d00..ac47cfbb 100644 --- a/lib/src/public/main/model/channel/group_channel_filter.dart +++ b/lib/src/public/main/model/channel/group_channel_filter.dart @@ -78,6 +78,10 @@ class GroupChannelFilter { @JsonKey(name: 'metadata_value_startswith') String? metaDataValueStartsWithFilter; + static GroupChannelFilter fromJson(Map json) { + return _$GroupChannelFilterFromJson(json); + } + Map toJson() { final json = _$GroupChannelFilterToJson(this); if (json['metadata_key'] == null) { diff --git a/lib/src/public/main/model/channel/notification_category.dart b/lib/src/public/main/model/channel/notification_category.dart index e15f0732..f3e757cc 100644 --- a/lib/src/public/main/model/channel/notification_category.dart +++ b/lib/src/public/main/model/channel/notification_category.dart @@ -29,7 +29,6 @@ class NotificationCategory { @override bool operator ==(other) { if (identical(other, this)) return true; - if (!(super == (other))) return false; return other is NotificationCategory && other.id == id && diff --git a/lib/src/public/main/model/message/message_change_logs.dart b/lib/src/public/main/model/message/message_change_logs.dart index 53e96cca..553f6e70 100644 --- a/lib/src/public/main/model/message/message_change_logs.dart +++ b/lib/src/public/main/model/message/message_change_logs.dart @@ -1,7 +1,12 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/utils/json_converter.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/base_channel/base_channel.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_channel.dart'; import 'package:sendbird_chat_sdk/src/public/core/message/base_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/notification_message.dart'; +import 'package:sendbird_chat_sdk/src/public/core/message/root_message.dart'; part 'message_change_logs.g.dart'; @@ -9,12 +14,14 @@ part 'message_change_logs.g.dart'; @JsonSerializable(createToJson: false) class MessageChangeLogs { /// The updated messages. - @JsonKey(defaultValue: [], name: 'updated') - final List updatedMessages; + /// [BaseMessage] for [BaseChannel], [NotificationMessage] for [FeedChannel]. + @JsonKey(fromJson: toRootMessageList, defaultValue: [], name: 'updated') + final List updatedMessages; /// The deleted message IDs. - @JsonKey(fromJson: _deletedMessageIds, name: 'deleted') - final List deletedMessageIds; + /// [int] for [BaseMessage], [String] for [NotificationMessage]. + @JsonKey(fromJson: toDeletedMessageIds, name: 'deleted') + final List deletedMessageIds; /// True if it has more changelogs. @JsonKey(defaultValue: false) @@ -34,6 +41,3 @@ class MessageChangeLogs { static MessageChangeLogs fromJson(Map json) => _$MessageChangeLogsFromJson(json); } - -List _deletedMessageIds(List json) => - json.map((e) => e['message_id'] as int).toList(); diff --git a/lib/src/public/main/model/message/message_change_logs.g.dart b/lib/src/public/main/model/message/message_change_logs.g.dart index a9c7faf5..92f693fd 100644 --- a/lib/src/public/main/model/message/message_change_logs.g.dart +++ b/lib/src/public/main/model/message/message_change_logs.g.dart @@ -8,11 +8,10 @@ part of 'message_change_logs.dart'; MessageChangeLogs _$MessageChangeLogsFromJson(Map json) => MessageChangeLogs( - updatedMessages: (json['updated'] as List?) - ?.map((e) => BaseMessage.fromJson(e as Map)) - .toList() ?? - [], - deletedMessageIds: _deletedMessageIds(json['deleted'] as List), + updatedMessages: json['updated'] == null + ? [] + : toRootMessageList(json['updated'] as List), + deletedMessageIds: toDeletedMessageIds(json['deleted'] as List), hasMore: json['has_more'] as bool? ?? false, token: json['next'] as String?, ); diff --git a/lib/src/public/main/model/message/message_meta_array.dart b/lib/src/public/main/model/message/message_meta_array.dart index c8d8a046..f79cbdd1 100644 --- a/lib/src/public/main/model/message/message_meta_array.dart +++ b/lib/src/public/main/model/message/message_meta_array.dart @@ -1,5 +1,6 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. +import 'package:collection/collection.dart'; import 'package:json_annotation/json_annotation.dart'; part 'message_meta_array.g.dart'; @@ -20,5 +21,22 @@ class MessageMetaArray { factory MessageMetaArray.fromJson(Map json) => _$MessageMetaArrayFromJson(json); + Map toJson() => _$MessageMetaArrayToJson(this); + + @override + bool operator ==(other) { + if (identical(other, this)) return true; + + final eq = const ListEquality().equals; + return other is MessageMetaArray && + other.key == key && + eq(other.value, value); + } + + @override + int get hashCode => Object.hash( + key, + value, + ); } diff --git a/lib/src/public/main/model/og/og_image.dart b/lib/src/public/main/model/og/og_image.dart index b1559ab9..b5170aec 100644 --- a/lib/src/public/main/model/og/og_image.dart +++ b/lib/src/public/main/model/og/og_image.dart @@ -17,7 +17,7 @@ abstract class OGDisplayable { /// Represents a Open Graph Image of [OGMetaData]. /// For Specifications, see [https://ogp.me/](https://ogp.me/). -@JsonSerializable(createToJson: false) +@JsonSerializable() class OGImage implements OGDisplayable, OGMedia { /// An image URL which represents the object within the Open Graph. @override @@ -52,4 +52,6 @@ class OGImage implements OGDisplayable, OGMedia { factory OGImage.fromJson(Map json) => _$OGImageFromJson(json); + + Map toJson() => _$OGImageToJson(this); } diff --git a/lib/src/public/main/model/og/og_image.g.dart b/lib/src/public/main/model/og/og_image.g.dart index 53670acf..76f29fd1 100644 --- a/lib/src/public/main/model/og/og_image.g.dart +++ b/lib/src/public/main/model/og/og_image.g.dart @@ -14,3 +14,12 @@ OGImage _$OGImageFromJson(Map json) => OGImage( width: json['width'] as int? ?? 0, height: json['height'] as int? ?? 0, ); + +Map _$OGImageToJson(OGImage instance) => { + 'url': instance.url, + 'secure_url': instance.secureUrl, + 'type': instance.type, + 'alt': instance.alt, + 'width': instance.width, + 'height': instance.height, + }; diff --git a/lib/src/public/main/model/og/og_meta_data.dart b/lib/src/public/main/model/og/og_meta_data.dart index 10871ae1..f9e64d14 100644 --- a/lib/src/public/main/model/og/og_meta_data.dart +++ b/lib/src/public/main/model/og/og_meta_data.dart @@ -8,7 +8,7 @@ part 'og_meta_data.g.dart'; /// Represents a OGMetaData of a url. /// For Specifications, see [https://ogp.me/](https://ogp.me/). /// Currently we only support images. -@JsonSerializable(createToJson: false) +@JsonSerializable() class OGMetaData { /// The title of the object as it should appear within the graph. ex: "The Rock". @JsonKey(name: 'og:title') @@ -39,4 +39,6 @@ class OGMetaData { factory OGMetaData.fromJson(Map json) => _$OGMetaDataFromJson(json); + + Map toJson() => _$OGMetaDataToJson(this); } diff --git a/lib/src/public/main/model/og/og_meta_data.g.dart b/lib/src/public/main/model/og/og_meta_data.g.dart index 9b94da7d..17bb977d 100644 --- a/lib/src/public/main/model/og/og_meta_data.g.dart +++ b/lib/src/public/main/model/og/og_meta_data.g.dart @@ -14,3 +14,11 @@ OGMetaData _$OGMetaDataFromJson(Map json) => OGMetaData( ? null : OGImage.fromJson(json['og:image'] as Map), ); + +Map _$OGMetaDataToJson(OGMetaData instance) => + { + 'og:title': instance.title, + 'og:url': instance.url, + 'og:description': instance.description, + 'og:image': instance.ogImage?.toJson(), + }; diff --git a/lib/src/public/main/model/reaction/reaction.dart b/lib/src/public/main/model/reaction/reaction.dart index e6adc8ba..2bd83432 100644 --- a/lib/src/public/main/model/reaction/reaction.dart +++ b/lib/src/public/main/model/reaction/reaction.dart @@ -8,7 +8,7 @@ import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction_event. part 'reaction.g.dart'; /// Objects representing a reaction. -@JsonSerializable(createToJson: false) +@JsonSerializable() class Reaction { /// The key of the reaction. final String key; @@ -54,6 +54,8 @@ class Reaction { factory Reaction.fromJson(Map json) => _$ReactionFromJson(json); + Map toJson() => _$ReactionToJson(this); + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/main/model/reaction/reaction.g.dart b/lib/src/public/main/model/reaction/reaction.g.dart index a7b4a45b..e8d29054 100644 --- a/lib/src/public/main/model/reaction/reaction.g.dart +++ b/lib/src/public/main/model/reaction/reaction.g.dart @@ -14,3 +14,9 @@ Reaction _$ReactionFromJson(Map json) => Reaction( [], updatedAt: json['updated_at'] as int, ); + +Map _$ReactionToJson(Reaction instance) => { + 'key': instance.key, + 'user_ids': instance.userIds, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/src/public/main/model/thread/thread_info.dart b/lib/src/public/main/model/thread/thread_info.dart index 69fd130c..c843433a 100644 --- a/lib/src/public/main/model/thread/thread_info.dart +++ b/lib/src/public/main/model/thread/thread_info.dart @@ -6,7 +6,7 @@ import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; part 'thread_info.g.dart'; /// Represents a thread info of a message. -@JsonSerializable(createToJson: false) +@JsonSerializable() class ThreadInfo { /// The total number of replies in a specific thread. /// A value of 0 indicates there is no reply in the thread. @@ -32,6 +32,8 @@ class ThreadInfo { factory ThreadInfo.fromJson(Map json) => _$ThreadInfoFromJson(json); + Map toJson() => _$ThreadInfoToJson(this); + @override bool operator ==(other) { if (identical(other, this)) return true; diff --git a/lib/src/public/main/model/thread/thread_info.g.dart b/lib/src/public/main/model/thread/thread_info.g.dart index 6bea5734..4253cbea 100644 --- a/lib/src/public/main/model/thread/thread_info.g.dart +++ b/lib/src/public/main/model/thread/thread_info.g.dart @@ -15,3 +15,11 @@ ThreadInfo _$ThreadInfoFromJson(Map json) => ThreadInfo( lastRepliedAt: json['last_replied_at'] as int? ?? 0, updatedAt: json['updated_at'] as int?, ); + +Map _$ThreadInfoToJson(ThreadInfo instance) => + { + 'reply_count': instance.replyCount, + 'most_replies': instance.mostRepliesUsers.map((e) => e.toJson()).toList(), + 'last_replied_at': instance.lastRepliedAt, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/src/public/main/params/channel/group_channel_create_params.dart b/lib/src/public/main/params/channel/group_channel_create_params.dart index 9c215f59..ea93058f 100644 --- a/lib/src/public/main/params/channel/group_channel_create_params.dart +++ b/lib/src/public/main/params/channel/group_channel_create_params.dart @@ -66,6 +66,10 @@ class GroupChannelCreateParams { GroupChannelCreateParams(); + static GroupChannelCreateParams fromJson(Map json) { + return _$GroupChannelCreateParamsFromJson(json); + } + Map toJson() { final json = _$GroupChannelCreateParamsToJson(this); if (coverImage != null && coverImage!.hasBinary) { diff --git a/lib/src/public/main/params/channel/group_channel_update_params.dart b/lib/src/public/main/params/channel/group_channel_update_params.dart index 122aa869..61733a1c 100644 --- a/lib/src/public/main/params/channel/group_channel_update_params.dart +++ b/lib/src/public/main/params/channel/group_channel_update_params.dart @@ -46,6 +46,10 @@ class GroupChannelUpdateParams { GroupChannelUpdateParams(); + static GroupChannelUpdateParams fromJson(Map json) { + return _$GroupChannelUpdateParamsFromJson(json); + } + Map toJson() { final json = _$GroupChannelUpdateParamsToJson(this); if (coverImage != null && coverImage!.hasBinary) { diff --git a/lib/src/public/main/params/channel/open_channel_create_params.dart b/lib/src/public/main/params/channel/open_channel_create_params.dart index 231a1e52..9edb2d1e 100644 --- a/lib/src/public/main/params/channel/open_channel_create_params.dart +++ b/lib/src/public/main/params/channel/open_channel_create_params.dart @@ -28,6 +28,10 @@ class OpenChannelCreateParams { @JsonKey(name: 'operator_ids') List? operatorUserIds; + static OpenChannelCreateParams fromJson(Map json) { + return _$OpenChannelCreateParamsFromJson(json); + } + Map toJson() { final json = _$OpenChannelCreateParamsToJson(this); if (coverImage != null && coverImage!.hasBinary) { diff --git a/lib/src/public/main/params/channel/open_channel_update_params.dart b/lib/src/public/main/params/channel/open_channel_update_params.dart index 8db1fced..de23d178 100644 --- a/lib/src/public/main/params/channel/open_channel_update_params.dart +++ b/lib/src/public/main/params/channel/open_channel_update_params.dart @@ -25,6 +25,10 @@ class OpenChannelUpdateParams { @JsonKey(name: 'operator_ids') List? operatorUserIds; + static OpenChannelUpdateParams fromJson(Map json) { + return _$OpenChannelUpdateParamsFromJson(json); + } + Map toJson() { final json = _$OpenChannelUpdateParamsToJson(this); if (coverImage != null && coverImage!.hasBinary) { diff --git a/lib/src/public/main/params/message/base_message_fetch_params.dart b/lib/src/public/main/params/message/base_message_fetch_params.dart index bacae30b..57adf7b1 100644 --- a/lib/src/public/main/params/message/base_message_fetch_params.dart +++ b/lib/src/public/main/params/message/base_message_fetch_params.dart @@ -25,6 +25,10 @@ class BaseMessageFetchParams { @JsonKey(name: 'include_reply_type') ReplyType? replyType; + static BaseMessageFetchParams fromJson(Map json) { + return _$BaseMessageFetchParamsFromJson(json); + } + Map toJson() { final json = _$BaseMessageFetchParamsToJson(this); if (replyType != null) { diff --git a/lib/src/public/main/params/message/message_change_logs_params.dart b/lib/src/public/main/params/message/message_change_logs_params.dart index 26231572..9710da54 100644 --- a/lib/src/public/main/params/message/message_change_logs_params.dart +++ b/lib/src/public/main/params/message/message_change_logs_params.dart @@ -9,6 +9,10 @@ part 'message_change_logs_params.g.dart'; /// An object consists a set of parameters to retrieve message's change log @JsonSerializable() class MessageChangeLogParams extends BaseMessageFetchParams { + static MessageChangeLogParams fromJson(Map json) { + return _$MessageChangeLogParamsFromJson(json); + } + @override Map toJson() => _$MessageChangeLogParamsToJson(this); } diff --git a/lib/src/public/main/params/message/message_list_params.dart b/lib/src/public/main/params/message/message_list_params.dart index 280843b0..f0708da3 100644 --- a/lib/src/public/main/params/message/message_list_params.dart +++ b/lib/src/public/main/params/message/message_list_params.dart @@ -45,6 +45,10 @@ class MessageListParams extends BaseMessageFetchParams { @JsonKey(name: 'show_subchannel_messages_only') bool showSubChannelMessagesOnly = false; + static MessageListParams fromJson(Map json) { + return _$MessageListParamsFromJson(json); + } + @override Map toJson() { final json = super.toJson(); diff --git a/lib/src/public/main/params/message/message_retrieval_params.dart b/lib/src/public/main/params/message/message_retrieval_params.dart index f47452ce..e6dd1a29 100644 --- a/lib/src/public/main/params/message/message_retrieval_params.dart +++ b/lib/src/public/main/params/message/message_retrieval_params.dart @@ -24,6 +24,10 @@ class MessageRetrievalParams extends BaseMessageFetchParams { required this.messageId, }); + static MessageRetrievalParams fromJson(Map json) { + return _$MessageRetrievalParamsFromJson(json); + } + @override Map toJson() { final json = super.toJson(); diff --git a/lib/src/public/main/params/message/scheduled_file_message_update_params.dart b/lib/src/public/main/params/message/scheduled_file_message_update_params.dart index 8fa04729..aec1a103 100644 --- a/lib/src/public/main/params/message/scheduled_file_message_update_params.dart +++ b/lib/src/public/main/params/message/scheduled_file_message_update_params.dart @@ -62,6 +62,10 @@ class ScheduledFileMessageUpdateParams { this.pushNotificationDeliveryOption = PushNotificationDeliveryOption.normal, }); + static ScheduledFileMessageUpdateParams fromJson(Map json) { + return _$ScheduledFileMessageUpdateParamsFromJson(json); + } + Map toJson() { var json = _$ScheduledFileMessageUpdateParamsToJson(this); json.removeWhere((key, value) => value == null); diff --git a/lib/src/public/main/params/message/scheduled_user_message_update_params.dart b/lib/src/public/main/params/message/scheduled_user_message_update_params.dart index b39f7ee1..93e64574 100644 --- a/lib/src/public/main/params/message/scheduled_user_message_update_params.dart +++ b/lib/src/public/main/params/message/scheduled_user_message_update_params.dart @@ -46,6 +46,10 @@ class ScheduledUserMessageUpdateParams { this.pushNotificationDeliveryOption, }); + static ScheduledUserMessageUpdateParams fromJson(Map json) { + return _$ScheduledUserMessageUpdateParamsFromJson(json); + } + Map toJson() { var json = _$ScheduledUserMessageUpdateParamsToJson(this); json.removeWhere((key, value) => value == null); diff --git a/lib/src/public/main/params/message/threaded_message_list_params.dart b/lib/src/public/main/params/message/threaded_message_list_params.dart index 32ab48ec..41a7fce5 100644 --- a/lib/src/public/main/params/message/threaded_message_list_params.dart +++ b/lib/src/public/main/params/message/threaded_message_list_params.dart @@ -39,6 +39,10 @@ class ThreadedMessageListParams extends BaseMessageFetchParams { /// When the user ID filtering is not needed, the value should be set to null. List? senderIds; + static ThreadedMessageListParams fromJson(Map json) { + return _$ThreadedMessageListParamsFromJson(json); + } + @override Map toJson() { replyType = ReplyType.all; diff --git a/lib/src/public/main/params/poll/poll_create_params.dart b/lib/src/public/main/params/poll/poll_create_params.dart index 6a0fe162..f2244ce2 100644 --- a/lib/src/public/main/params/poll/poll_create_params.dart +++ b/lib/src/public/main/params/poll/poll_create_params.dart @@ -38,6 +38,10 @@ class PollCreateParams { this.closeAt = -1, // Default value }); + static PollCreateParams fromJson(Map json) { + return _$PollCreateParamsFromJson(json); + } + Map toJson() { final json = _$PollCreateParamsToJson(this); json.removeWhere((key, value) => value == null); diff --git a/lib/src/public/main/params/poll/poll_update_params.dart b/lib/src/public/main/params/poll/poll_update_params.dart index 9ea1d814..ff6c0efd 100644 --- a/lib/src/public/main/params/poll/poll_update_params.dart +++ b/lib/src/public/main/params/poll/poll_update_params.dart @@ -31,6 +31,10 @@ class PollUpdateParams { this.closeAt = -1, }); + static PollUpdateParams fromJson(Map json) { + return _$PollUpdateParamsFromJson(json); + } + Map toJson() { return _$PollUpdateParamsToJson(this); } diff --git a/lib/src/public/main/query/message/previous_message_list_query.dart b/lib/src/public/main/query/message/previous_message_list_query.dart index fb5ff9c6..504188fe 100644 --- a/lib/src/public/main/query/message/previous_message_list_query.dart +++ b/lib/src/public/main/query/message/previous_message_list_query.dart @@ -99,7 +99,7 @@ class PreviousMessageListQuery extends BaseQuery { timestamp: messageTimestamp ?? 0, ), ); - final messages = res.messages; + final messages = List.from(res.messages); if (messages.isNotEmpty) { final oldestMessage = reverse ? messages.last : messages.first; diff --git a/pubspec.yaml b/pubspec.yaml index e1363275..d6a44505 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: sendbird_chat_sdk description: With Sendbird Chat for Flutter, you can easily build an in-app chat with all the essential messaging features. -version: 4.0.13 +version: 4.1.0 homepage: https://sendbird.com repository: https://github.com/sendbird/sendbird-chat-sdk-flutter documentation: https://sendbird.com/docs/chat/sdk/v4/flutter/getting-started/send-first-message