From ccc12b813d88066addb615b020f84e3a39dd6dfd Mon Sep 17 00:00:00 2001 From: Tyler Jeong Date: Fri, 30 Jun 2023 10:27:48 +0900 Subject: [PATCH] Add 4.0.3. --- CHANGELOG.md | 38 +++ README.md | 2 +- lib/sendbird_chat_sdk.dart | 20 +- lib/src/internal/main/chat/chat.dart | 13 +- lib/src/internal/main/chat/chat_channel.dart | 40 ++- .../main/chat/chat_notifications.dart | 36 +++ .../main/chat_cache/cache_service.dart | 2 +- .../chat_cache/channel/meta_data_cache.dart | 4 +- .../main/chat_context/chat_context.dart | 9 +- .../collection_manager.dart | 169 +++++++++- .../message_collection_manager.dart | 151 ++++++--- .../main/chat_manager/command_manager.dart | 65 ++-- .../main/chat_manager/event_manager.dart | 53 ++- .../internal/main/extensions/extensions.dart | 17 + .../internal/main/model/delivery_status.dart | 6 +- lib/src/internal/main/model/read_status.dart | 4 +- .../internal/main/model/read_status.g.dart | 1 + .../internal/main/model/typing_status.dart | 10 +- ...fo.dart => unread_message_count_info.dart} | 30 +- ....dart => unread_message_count_info.g.dart} | 8 +- .../feed_channel_change_logs_request.dart | 51 +++ .../feed_channel_list_request.dart | 50 +++ .../feed_channel_refresh_request.dart | 38 +++ ...=> group_channel_change_logs_request.dart} | 2 + .../group_channel_list_request.dart | 6 +- .../group_channel_refresh_request.dart | 7 +- .../open_channel_list_request.dart | 3 +- .../open_channel_refresh_request.dart | 1 - ...ification_channel_setting_get_request.dart | 24 ++ .../notification_template_get_request.dart | 23 ++ ...otification_template_list_get_request.dart | 44 +++ .../user_unread_message_count_request.dart | 14 +- .../http/http_client/response/responses.dart | 44 +++ .../http_client/response/responses.g.dart | 10 + .../websocket/event/channel_event.g.dart | 1 + .../websocket/event/message_event.g.dart | 1 + .../channel/base_channel/base_channel.dart | 144 ++++++--- .../base_channel/base_channel_message.dart | 45 ++- .../base_channel_message_meta_array.dart | 4 + .../base_channel_meta_counters.dart | 8 + .../base_channel/base_channel_meta_data.dart | 7 + .../base_channel/base_channel_moderation.dart | 8 + .../base_channel/base_channel_operator.dart | 3 + .../base_channel/base_channel_reaction.dart | 2 + .../channel/feed_channel/feed_channel.dart | 158 +++++++++ .../channel/group_channel/group_channel.dart | 68 ++-- .../group_channel/group_channel.g.dart | 1 + .../channel/open_channel/open_channel.dart | 13 +- .../public/core/message/admin_message.g.dart | 1 + lib/src/public/core/message/file_message.dart | 4 + .../public/core/message/file_message.g.dart | 1 + lib/src/public/core/message/user_message.dart | 5 + .../public/core/message/user_message.g.dart | 1 + lib/src/public/main/chat/sendbird_chat.dart | 62 ++++ .../base_channel_context.dart | 15 + .../feed_channel_context.dart | 14 + .../group_channel_context.dart | 12 +- .../base_message_collection.dart | 305 ++++++++++++++++++ .../base_message_collection_handler.dart | 5 + .../base_message_context.dart | 23 ++ .../message_collection.dart | 300 +---------------- .../message_collection_handler.dart | 5 +- .../message_context.dart | 15 +- .../notification_collection.dart | 40 +++ .../notification_collection_handler.dart | 76 +++++ .../notification_context.dart | 16 + lib/src/public/main/define/enums.dart | 15 +- .../public/main/handler/channel_handler.dart | 21 +- .../main/handler/user_event_handler.dart | 13 +- .../channel/feed_channel_change_logs.dart | 46 +++ .../channel/feed_channel_change_logs.g.dart | 22 ++ .../global_notification_channel_setting.dart | 15 + .../model/chat/notification_template.dart | 13 + .../chat/notification_template_list.dart | 25 ++ lib/src/public/main/model/info/app_info.dart | 27 +- .../public/main/model/info/app_info.g.dart | 10 +- lib/src/public/main/model/info/file_info.dart | 5 +- .../main/model/info/notification_info.dart | 38 +++ .../main/model/info/notification_info.g.dart | 15 + .../model/message/unread_message_count.dart | 22 ++ .../main/model/reaction/reaction_event.g.dart | 1 + .../thread/thread_info_updated_event.g.dart | 1 + .../feed_channel_change_logs_params.dart | 16 + .../group_channel_change_logs_params.dart | 5 + .../message/base_message_create_params.dart | 6 +- .../message/file_message_create_params.dart | 21 +- .../params/message/message_list_params.g.dart | 4 +- .../message/message_retrieval_params.g.dart | 1 + .../threaded_message_list_params.g.dart | 4 +- .../message/user_message_create_params.dart | 5 +- .../notification_template_list_params.dart | 25 ++ .../channel/feed_channel_list_query.dart | 65 ++++ .../channel/group_channel_list_query.dart | 7 + .../channel/open_channel_list_query.dart | 2 +- .../public_group_channel_list_query.dart | 1 + .../message/scheduled_message_list_query.dart | 2 +- pubspec.yaml | 4 +- 97 files changed, 2226 insertions(+), 589 deletions(-) create mode 100644 lib/src/internal/main/chat/chat_notifications.dart rename lib/src/internal/main/model/{unread_count_info.dart => unread_message_count_info.dart} (62%) rename lib/src/internal/main/model/{unread_count_info.g.dart => unread_message_count_info.g.dart} (69%) create mode 100644 lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_change_logs_request.dart create mode 100644 lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_list_request.dart create mode 100644 lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_refresh_request.dart rename lib/src/internal/network/http/http_client/request/channel/group_channel/{group_channel_change_log_request.dart => group_channel_change_logs_request.dart} (97%) create mode 100644 lib/src/internal/network/http/http_client/request/main/notifications/global_notification_channel_setting_get_request.dart create mode 100644 lib/src/internal/network/http/http_client/request/main/notifications/notification_template_get_request.dart create mode 100644 lib/src/internal/network/http/http_client/request/main/notifications/notification_template_list_get_request.dart create mode 100644 lib/src/public/core/channel/feed_channel/feed_channel.dart create mode 100644 lib/src/public/main/collection/group_channel_collection/base_channel_context.dart create mode 100644 lib/src/public/main/collection/group_channel_collection/feed_channel_context.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/base_message_context.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart create mode 100644 lib/src/public/main/collection/group_channel_message_collection/notification_context.dart create mode 100644 lib/src/public/main/model/channel/feed_channel_change_logs.dart create mode 100644 lib/src/public/main/model/channel/feed_channel_change_logs.g.dart create mode 100644 lib/src/public/main/model/chat/global_notification_channel_setting.dart create mode 100644 lib/src/public/main/model/chat/notification_template.dart create mode 100644 lib/src/public/main/model/chat/notification_template_list.dart create mode 100644 lib/src/public/main/model/info/notification_info.dart create mode 100644 lib/src/public/main/model/info/notification_info.g.dart create mode 100644 lib/src/public/main/model/message/unread_message_count.dart create mode 100644 lib/src/public/main/params/channel/feed_channel_change_logs_params.dart create mode 100644 lib/src/public/main/params/notifications/notification_template_list_params.dart create mode 100644 lib/src/public/main/query/channel/feed_channel_list_query.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3eba26..b4141951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +## v4.0.3 (Jun 30, 2023) + +### Features + +#### FeedChannel +- Added `FeedChannelListQuery` +- Added `FeedChannel`. +- Added `feed` in `ChannelType`. +- Added `getMyFeedChannelChangeLogs()` with `FeedChannelChangeLogsParams` in SendbirdChat. +- Added `getTotalUnreadMessageCountWithFeedChannel() + ` in SendbirdChat. +- Added `FeedChannelHandler`. +- Added `onTotalUnreadMessageCountChanged()` in `UserEventHandler` and `UnreadMessageCount`. + +#### Collection for notifications +- Added `NotificationCollection`, `NotificationCollectionHandler` and `NotificationContext`. +- Added `BaseMessageCollection`, `BaseMessageCollectionHandler` and `BaseMessageContext`. +- Added `FeedChannelContext`, `BaseChannelContext`. + +#### ChatNotification for GroupChannel +- Added `isChatNotification` in GroupChannel. +- Added `includeChatNotification` in `GroupChannelListQuery` and `GroupChannelChangeLogsParams`. + +#### Setting and Template for Notification +- Added `getGlobalNotificationChannelSetting() + ` and `GlobalNotificationChannelSetting` in SendbirdChat. +- Added `getNotificationTemplateListByToken() + ` with `NotificationTemplateListParams` and `NotificationTemplateList` in SendbirdChat. +- Added `getNotificationTemplate() + ` and `NotificationTemplate` in SendbirdChat. + +#### NotificationInfo +- Added `NotificationInfo`. +- Added `notificationInfo` in `AppInfo`. + +### Improvements +- Improved stability. + ## v4.0.2 (Jun 23, 2023) - Improved stability. diff --git a/README.md b/README.md index d416c61c..bec5560b 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.2 + sendbird_chat_sdk: ^4.0.3 ``` - Run `flutter pub get` command in your project directory. diff --git a/lib/sendbird_chat_sdk.dart b/lib/sendbird_chat_sdk.dart index 64825f75..86cb69dd 100644 --- a/lib/sendbird_chat_sdk.dart +++ b/lib/sendbird_chat_sdk.dart @@ -4,6 +4,7 @@ library sendbird_chat_sdk; export 'src/public/core/channel/base_channel/base_channel.dart'; +export 'src/public/core/channel/feed_channel/feed_channel.dart'; export 'src/public/core/channel/group_channel/group_channel.dart'; export 'src/public/core/channel/open_channel/open_channel.dart'; export 'src/public/core/message/admin_message.dart'; @@ -17,12 +18,20 @@ export 'src/public/core/user/user.dart'; export 'src/public/main/chat/sendbird_chat.dart'; export 'src/public/main/chat/sendbird_chat_options.dart'; export 'src/public/main/collection/collection_event_source.dart'; -export 'src/public/main/collection/group_channel_collection/group_channel_context.dart'; +export 'src/public/main/collection/group_channel_collection/base_channel_context.dart'; +export 'src/public/main/collection/group_channel_collection/feed_channel_context.dart'; export 'src/public/main/collection/group_channel_collection/group_channel_collection.dart'; export 'src/public/main/collection/group_channel_collection/group_channel_collection_handler.dart'; +export 'src/public/main/collection/group_channel_collection/group_channel_context.dart'; +export 'src/public/main/collection/group_channel_message_collection/base_message_collection.dart'; +export 'src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart'; +export 'src/public/main/collection/group_channel_message_collection/base_message_context.dart'; export 'src/public/main/collection/group_channel_message_collection/message_collection.dart'; export 'src/public/main/collection/group_channel_message_collection/message_collection_handler.dart'; export 'src/public/main/collection/group_channel_message_collection/message_context.dart'; +export 'src/public/main/collection/group_channel_message_collection/notification_collection.dart'; +export 'src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart'; +export 'src/public/main/collection/group_channel_message_collection/notification_context.dart'; export 'src/public/main/define/enums.dart'; export 'src/public/main/define/exceptions.dart'; export 'src/public/main/define/sendbird_error.dart'; @@ -30,19 +39,25 @@ export 'src/public/main/handler/channel_handler.dart'; export 'src/public/main/handler/connection_handler.dart'; export 'src/public/main/handler/session_handler.dart'; export 'src/public/main/handler/user_event_handler.dart'; +export 'src/public/main/model/channel/feed_channel_change_logs.dart'; export 'src/public/main/model/channel/group_channel_change_logs.dart'; export 'src/public/main/model/channel/group_channel_filter.dart'; export 'src/public/main/model/channel/group_channel_unread_item_count.dart'; export 'src/public/main/model/chat/do_not_disturb.dart'; export 'src/public/main/model/chat/emoji.dart'; +export 'src/public/main/model/chat/global_notification_channel_setting.dart'; +export 'src/public/main/model/chat/notification_template.dart'; +export 'src/public/main/model/chat/notification_template_list.dart'; export 'src/public/main/model/chat/snooze_period.dart'; export 'src/public/main/model/info/app_info.dart'; export 'src/public/main/model/info/file_info.dart'; export 'src/public/main/model/info/mute_info.dart'; +export 'src/public/main/model/info/notification_info.dart'; export 'src/public/main/model/info/scheduled_info.dart'; export 'src/public/main/model/message/apple_critical_alert_options.dart'; export 'src/public/main/model/message/message_change_logs.dart'; export 'src/public/main/model/message/message_meta_array.dart'; +export 'src/public/main/model/message/unread_message_count.dart'; export 'src/public/main/model/og/og_image.dart'; export 'src/public/main/model/og/og_meta_data.dart'; export 'src/public/main/model/poll/poll.dart'; @@ -56,6 +71,7 @@ export 'src/public/main/model/reaction/reaction_event.dart'; export 'src/public/main/model/thread/thread_info.dart'; export 'src/public/main/model/thread/thread_info_updated_event.dart'; export 'src/public/main/model/thread/threaded_messages.dart'; +export 'src/public/main/params/channel/feed_channel_change_logs_params.dart'; export 'src/public/main/params/channel/group_channel_change_logs_params.dart'; export 'src/public/main/params/channel/group_channel_create_params.dart'; export 'src/public/main/params/channel/group_channel_total_unread_channel_count_params.dart'; @@ -81,6 +97,7 @@ export 'src/public/main/params/message/threaded_message_list_params.dart'; export 'src/public/main/params/message/total_scheduled_message_count_params.dart'; export 'src/public/main/params/message/user_message_create_params.dart'; export 'src/public/main/params/message/user_message_update_params.dart'; +export 'src/public/main/params/notifications/notification_template_list_params.dart'; export 'src/public/main/params/poll/poll_create_params.dart'; export 'src/public/main/params/poll/poll_list_query_params.dart'; export 'src/public/main/params/poll/poll_option_retrieval_params.dart'; @@ -88,6 +105,7 @@ export 'src/public/main/params/poll/poll_retrieval_params.dart'; export 'src/public/main/params/poll/poll_update_params.dart'; export 'src/public/main/params/poll/poll_voter_list_query_params.dart'; export 'src/public/main/query/base_query.dart'; +export 'src/public/main/query/channel/feed_channel_list_query.dart'; export 'src/public/main/query/channel/group_channel_list_query.dart'; export 'src/public/main/query/channel/open_channel_list_query.dart'; export 'src/public/main/query/channel/public_group_channel_list_query.dart'; diff --git a/lib/src/internal/main/chat/chat.dart b/lib/src/internal/main/chat/chat.dart index 5358f236..7488617b 100644 --- a/lib/src/internal/main/chat/chat.dart +++ b/lib/src/internal/main/chat/chat.dart @@ -18,7 +18,8 @@ import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart' import 'package:sendbird_chat_sdk/src/internal/main/utils/async/async_queue.dart'; 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/group_channel/group_channel_change_log_request.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/scheduled_message/group_channel_scheduled_message_total_count_request.dart'; @@ -26,6 +27,9 @@ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/emoji/emoji_category_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/emoji/emoji_container_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/emoji/emoji_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/notifications/global_notification_channel_setting_get_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/notifications/notification_template_get_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/notifications/notification_template_list_get_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/user/block/user_block_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/user/block/user_unblock_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/user/count/user_group_channel_count_request.dart'; @@ -46,10 +50,11 @@ part 'chat_channel.dart'; part 'chat_connection.dart'; part 'chat_emoji.dart'; part 'chat_event_handler.dart'; +part 'chat_notifications.dart'; part 'chat_push.dart'; part 'chat_user.dart'; -const sdkVersion = '4.0.2'; +const sdkVersion = '4.0.3'; // Internal implementation for main class. Do not directly access this class. class Chat with WidgetsBindingObserver { @@ -61,8 +66,9 @@ class Chat with WidgetsBindingObserver { // Extra data static const extraDataPremiumFeatureList = 'premium_feature_list'; static const extraDataFileUploadSizeLimit = 'file_upload_size_limit'; - static const extraDataEmojiHash = 'emoji_hash'; static const extraDataApplicationAttributes = 'application_attributes'; + static const extraDataEmojiHash = 'emoji_hash'; + static const extraDataNotifications = 'notifications'; static int globalChatId = 0; @@ -72,6 +78,7 @@ class Chat with WidgetsBindingObserver { extraDataFileUploadSizeLimit, extraDataApplicationAttributes, extraDataEmojiHash, + extraDataNotifications, ]; bool? _isObserverRegistered; diff --git a/lib/src/internal/main/chat/chat_channel.dart b/lib/src/internal/main/chat/chat_channel.dart index 1f7530e5..74cc7c10 100644 --- a/lib/src/internal/main/chat/chat_channel.dart +++ b/lib/src/internal/main/chat/chat_channel.dart @@ -32,10 +32,27 @@ extension ChatChannel on Chat { return res; } + Future getMyFeedChannelChangeLogs( + FeedChannelChangeLogsParams params, { + String? token, + int? timestamp, + }) async { + sbLog.i(StackTrace.current, 'token: $token'); + + final res = await apiClient.send( + FeedChannelChangeLogsGetRequest(this, params, + token: token, timestamp: timestamp)); + + for (final element in res.updatedChannels) { + element.saveToCache(this); + } + return res; + } + Future markAsReadAll() async { sbLog.i(StackTrace.current); - await apiClient.send(GroupChannelMarkAsReadRequest(this, - userId: chatContext.currentUserId)); + await apiClient.send( + GroupChannelMarkAsReadRequest(this, userId: chatContext.currentUserId)); } Future markAsRead({required List channelUrls}) async { @@ -101,36 +118,37 @@ extension ChatChannel on Chat { return result; } - Future getTotalUnreadMessageCount( + Future getTotalUnreadMessageCount( [GroupChannelTotalUnreadMessageCountParams? params]) async { - final result = await apiClient - .send(UserTotalUnreadMessageCountGetRequest(this, params: params)); + final result = await apiClient.send( + UserTotalUnreadMessageCountGetRequest(this, params: params)); sbLog.i(StackTrace.current, 'return: $result'); return result; } - Future getUnreadItemCount(List keys) async { - final result = await apiClient - .send(UserUnreadItemCountGetRequest(this, keys)); + Future getUnreadItemCount( + List keys) async { + final result = await apiClient.send( + UserUnreadItemCountGetRequest(this, keys)); sbLog.i(StackTrace.current, 'keys: $keys, return: $result'); return result; } int get subscribedTotalUnreadMessageCount { - final result = chatContext.unreadCountInfo.all; + final result = chatContext.unreadMessageCountInfo.all; sbLog.i(StackTrace.current, 'return: $result'); return result; } int get subscribedCustomTypeTotalUnreadMessageCount { final result = - chatContext.unreadCountInfo.customTypes.values.reduce((a, b) => a + b); + chatContext.unreadMessageCountInfo.customTypes.values.reduce((a, b) => a + b); sbLog.i(StackTrace.current, 'return: $result'); return result; } int? subscribedCustomTypeUnreadMessageCount(String customType) { - final result = chatContext.unreadCountInfo.customTypes[customType]; + final result = chatContext.unreadMessageCountInfo.customTypes[customType]; sbLog.i(StackTrace.current, 'customType: $customType, return: $result'); return result; } diff --git a/lib/src/internal/main/chat/chat_notifications.dart b/lib/src/internal/main/chat/chat_notifications.dart new file mode 100644 index 00000000..c7bf183a --- /dev/null +++ b/lib/src/internal/main/chat/chat_notifications.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +part of 'chat.dart'; + +extension ChatNotifications on Chat { + Future + getGlobalNotificationChannelSetting() async { + sbLog.i(StackTrace.current); + return await apiClient.send( + GlobalNotificationChannelSettingGetRequest(this)); + } + + Future getNotificationTemplateListByToken( + NotificationTemplateListParams params, { + String? token, + }) async { + sbLog.i(StackTrace.current); + return await apiClient + .send(NotificationTemplateListGetRequest( + this, + params, + token: token, + )); + } + + Future getNotificationTemplate({ + required String key, + }) async { + sbLog.i(StackTrace.current); + return await apiClient + .send(NotificationTemplateGetRequest( + this, + key: key, + )); + } +} diff --git a/lib/src/internal/main/chat_cache/cache_service.dart b/lib/src/internal/main/chat_cache/cache_service.dart index 05ad75ba..a35bdc8b 100644 --- a/lib/src/internal/main/chat_cache/cache_service.dart +++ b/lib/src/internal/main/chat_cache/cache_service.dart @@ -44,7 +44,7 @@ abstract class Cacheable { String get key; bool dirty = false; - void copyWith(others); + void copyWith(dynamic other); } extension Operation on Cacheable { diff --git a/lib/src/internal/main/chat_cache/channel/meta_data_cache.dart b/lib/src/internal/main/chat_cache/channel/meta_data_cache.dart index 7c4c9068..9d628314 100644 --- a/lib/src/internal/main/chat_cache/channel/meta_data_cache.dart +++ b/lib/src/internal/main/chat_cache/channel/meta_data_cache.dart @@ -62,8 +62,8 @@ class MetaDataCache implements Cacheable, Evictable { bool dirty = false; @override - void copyWith(others) { - _channelType = others._channelType; + void copyWith(dynamic other) { + _channelType = other._channelType; } @override diff --git a/lib/src/internal/main/chat_context/chat_context.dart b/lib/src/internal/main/chat_context/chat_context.dart index 3a674457..ca14c256 100644 --- a/lib/src/internal/main/chat_context/chat_context.dart +++ b/lib/src/internal/main/chat_context/chat_context.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:sendbird_chat_sdk/src/internal/main/model/reconnect_configuration.dart'; import 'package:sendbird_chat_sdk/src/internal/main/model/reconnect_task.dart'; -import 'package:sendbird_chat_sdk/src/internal/main/model/unread_count_info.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/model/unread_message_count_info.dart'; import 'package:sendbird_chat_sdk/src/public/core/user/user.dart'; import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat_options.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/info/app_info.dart'; @@ -69,8 +69,8 @@ class ChatContext { } // UnreadCountInfo - UnreadCountInfo unreadCountInfo = - UnreadCountInfo(all: 0, customTypes: {}, ts: 0); + UnreadMessageCountInfo unreadMessageCountInfo = + UnreadMessageCountInfo(all: 0, customTypes: {}, ts: 0); void resetReconnectTask() { final config = reconnectConfig; @@ -102,6 +102,7 @@ class ChatContext { wsHost = null; apiHeaders.clear(); - unreadCountInfo = UnreadCountInfo(all: 0, customTypes: {}, ts: 0); + unreadMessageCountInfo = + UnreadMessageCountInfo(all: 0, customTypes: {}, ts: 0); } } 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 a15dbc0c..227a1306 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 @@ -15,6 +15,8 @@ part 'message_collection_manager.dart'; class CollectionManager { final identifierForInternalGroupChannelHandlerForCollectionManager = 'InternalGroupChannelHandlerForCollectionManager_${const Uuid().v1()}'; + final identifierForInternalFeedChannelHandlerForCollectionManager = + 'InternalFeedChannelHandlerForCollectionManager_${const Uuid().v1()}'; final Chat _chat; @@ -23,7 +25,7 @@ class CollectionManager { int lastRequestTsForGroupChannelChangeLogs = 0; // MessageCollection - final List messageCollections = []; + final List messageCollections = []; final Map lastRequestTsForMessageChangeLogs = {}; final Map lastRequestTsForPollChangeLogs = {}; final Map lastRequestTsForMessagesGap = {}; @@ -33,6 +35,10 @@ class CollectionManager { identifierForInternalGroupChannelHandlerForCollectionManager, InternalGroupChannelHandlerForCollectionManager(this), ); + _chat.eventManager.addInternalChannelHandler( + identifierForInternalFeedChannelHandlerForCollectionManager, + InternalFeedChannelHandlerForCollectionManager(this), + ); } //------------------------------// @@ -243,10 +249,10 @@ class InternalGroupChannelHandlerForCollectionManager void onMessageReceived(BaseChannel channel, BaseMessage message) async { if (channel is GroupChannel) { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventMessageReceived, sendingStatus: SendingStatus.succeeded, addedMessages: [message], @@ -261,10 +267,10 @@ class InternalGroupChannelHandlerForCollectionManager void onMessageUpdated(BaseChannel channel, BaseMessage message) async { if (channel is GroupChannel) { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventMessageUpdated, sendingStatus: SendingStatus.succeeded, updatedMessages: [message], @@ -279,10 +285,10 @@ class InternalGroupChannelHandlerForCollectionManager void onMessageDeleted(BaseChannel channel, int messageId) async { if (channel is GroupChannel) { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventMessageDeleted, sendingStatus: SendingStatus.succeeded, deletedMessageIds: [messageId], @@ -297,14 +303,14 @@ class InternalGroupChannelHandlerForCollectionManager void onReactionUpdated(BaseChannel channel, ReactionEvent event) async { if (channel is GroupChannel) { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { if (message.messageId == event.messageId) { message.applyReactionEvent(event); _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventReactionUpdated, sendingStatus: SendingStatus.succeeded, updatedMessages: [message], @@ -323,14 +329,14 @@ class InternalGroupChannelHandlerForCollectionManager BaseChannel channel, ThreadInfoUpdateEvent event) async { if (channel is GroupChannel) { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { if (message.messageId == event.targetMessageId) { message.applyThreadInfoUpdateEvent(event); _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventThreadInfoUpdated, sendingStatus: SendingStatus.succeeded, updatedMessages: [message], @@ -456,7 +462,7 @@ class InternalGroupChannelHandlerForCollectionManager @override void onPollVoted(GroupChannel channel, PollVoteEvent event) async { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { if (message.messageId == event.messageId) { if (message is UserMessage && message.poll != null) { @@ -465,7 +471,7 @@ class InternalGroupChannelHandlerForCollectionManager _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventPollVoted, sendingStatus: SendingStatus.succeeded, updatedMessages: [message], @@ -481,7 +487,7 @@ class InternalGroupChannelHandlerForCollectionManager @override void onPollUpdated(GroupChannel channel, PollUpdateEvent event) async { for (final messageCollection in _collectionManager.messageCollections) { - if (messageCollection.channel.channelUrl == channel.channelUrl) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { for (final message in messageCollection.messageList) { if (message.messageId == event.messageId) { if (message is UserMessage && message.poll != null) { @@ -490,7 +496,7 @@ class InternalGroupChannelHandlerForCollectionManager _collectionManager.sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.eventPollUpdated, sendingStatus: SendingStatus.succeeded, updatedMessages: [message], @@ -508,3 +514,136 @@ class InternalGroupChannelHandlerForCollectionManager // onPollDeleted() => onMessageDeleted() } } + +//------------------------------// +// FeedChannelHandler for collectionManager +//------------------------------// +class InternalFeedChannelHandlerForCollectionManager + extends FeedChannelHandler { + final CollectionManager _collectionManager; + + InternalFeedChannelHandlerForCollectionManager( + CollectionManager collectionManager) + : _collectionManager = collectionManager; + +//------------------------------// +// BaseChannelHandler - channel +//------------------------------// + @override + void onMentionReceived(BaseChannel channel, BaseMessage message) { + if (channel is FeedChannel) { + for (final messageCollection in _collectionManager.messageCollections) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { + _collectionManager.sendEventsToMessageCollectionList( + eventSource: CollectionEventSource.eventMentionReceived, + updatedChannels: [channel], + ); + break; + } + } + } + } + + @override + void onChannelChanged(BaseChannel channel) { + if (channel is FeedChannel) { + for (final messageCollection in _collectionManager.messageCollections) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { + _collectionManager.sendEventsToMessageCollectionList( + eventSource: CollectionEventSource.eventChannelChanged, + updatedChannels: [channel], + ); + break; + } + } + } + } + + @override + void onChannelDeleted(String channelUrl, ChannelType channelType) { + if (channelType == ChannelType.feed) { + for (final messageCollection in _collectionManager.messageCollections) { + if (messageCollection.baseChannel.channelUrl == channelUrl) { + _collectionManager.sendEventsToMessageCollectionList( + eventSource: CollectionEventSource.eventChannelDeleted, + deletedChannelUrls: [channelUrl], + ); + break; + } + } + } + } + +//------------------------------// +// BaseChannelHandler - message +//------------------------------// + @override + void onMessageReceived(BaseChannel channel, BaseMessage message) async { + if (channel is FeedChannel) { + for (final messageCollection in _collectionManager.messageCollections) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { + _collectionManager.sendEventsToMessageCollection( + messageCollection: messageCollection, + baseChannel: channel, + eventSource: CollectionEventSource.eventMessageReceived, + sendingStatus: SendingStatus.succeeded, + addedMessages: [message], + ); + break; + } + } + } + } + + @override + void onMessageUpdated(BaseChannel channel, BaseMessage message) async { + if (channel is FeedChannel) { + for (final messageCollection in _collectionManager.messageCollections) { + 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.messageCollections) { + if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { + _collectionManager.sendEventsToMessageCollection( + messageCollection: messageCollection, + baseChannel: channel, + eventSource: CollectionEventSource.eventMessageDeleted, + sendingStatus: SendingStatus.succeeded, + deletedMessageIds: [messageId], + ); + break; + } + } + } + } + +//------------------------------// +// FeedChannelHandler - channel +//------------------------------// +// @override +// void onReadStatusUpdated(FeedChannel channel) { +// for (final messageCollection in _collectionManager.messageCollections) { +// if (messageCollection.baseChannel.channelUrl == channel.channelUrl) { +// _collectionManager.sendEventsToMessageCollectionList( +// eventSource: CollectionEventSource.eventReadStatusUpdated, +// updatedChannels: [channel], +// ); +// break; +// } +// } +// } +} 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 8d193cd3..8f4aa0f7 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 @@ -6,16 +6,16 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// // Add and remove message collection //------------------------------// - void addMessageCollection(MessageCollection collection) { + void addMessageCollection(BaseMessageCollection collection) { messageCollections.add(collection); final now = DateTime.now().millisecondsSinceEpoch; - lastRequestTsForMessageChangeLogs[collection.channel.channelUrl] = now; - lastRequestTsForPollChangeLogs[collection.channel.channelUrl] = now; - lastRequestTsForMessagesGap[collection.channel.channelUrl] = now; + lastRequestTsForMessageChangeLogs[collection.baseChannel.channelUrl] = now; + lastRequestTsForPollChangeLogs[collection.baseChannel.channelUrl] = now; + lastRequestTsForMessagesGap[collection.baseChannel.channelUrl] = now; } - void removeMessageCollection(MessageCollection collection) { + void removeMessageCollection(BaseMessageCollection collection) { messageCollections.remove(collection); } @@ -28,7 +28,7 @@ extension MessageCollectionManager on CollectionManager { for (final messageCollection in messageCollections) { sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: messageCollection.channel, + baseChannel: messageCollection.baseChannel, eventSource: CollectionEventSource.eventMessageSent, sendingStatus: SendingStatus.succeeded, addedMessages: [message], @@ -40,8 +40,9 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// // Message changeLogs //------------------------------// - void _requestMessageChangeLogs(MessageCollection messageCollection) async { - final channel = messageCollection.channel; + void _requestMessageChangeLogs( + BaseMessageCollection messageCollection) async { + final channel = messageCollection.baseChannel; final params = MessageChangeLogParams(); final List updatedMessages = []; final List deletedMessageIds = []; @@ -73,7 +74,7 @@ extension MessageCollectionManager on CollectionManager { sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.messageChangeLogs, sendingStatus: SendingStatus.succeeded, updatedMessages: updatedMessages, @@ -84,14 +85,18 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// // Poll changeLogs //------------------------------// - void _requestPollChangeLogs(MessageCollection messageCollection) async { + void _requestPollChangeLogs(BaseMessageCollection messageCollection) async { if (_chat.chatContext.appInfo == null || _chat.chatContext.appInfo!.premiumFeatureList.contains('poll') == false) { return; } - final channel = messageCollection.channel; + if (messageCollection.baseChannel is! GroupChannel) { + return; + } + + final groupChannel = messageCollection.baseChannel as GroupChannel; final List updatedMessages = []; final List deletedMessageIds = []; @@ -102,11 +107,11 @@ extension MessageCollectionManager on CollectionManager { final now = DateTime.now().millisecondsSinceEpoch; if (token == null) { - changeLogs = await channel.getPollChangeLogsSinceTimestamp( - lastRequestTsForPollChangeLogs[channel.channelUrl] ?? 0, + changeLogs = await groupChannel.getPollChangeLogsSinceTimestamp( + lastRequestTsForPollChangeLogs[groupChannel.channelUrl] ?? 0, ); } else { - changeLogs = await channel.getPollChangeLogsSinceToken( + changeLogs = await groupChannel.getPollChangeLogsSinceToken( token, ); } @@ -125,13 +130,13 @@ extension MessageCollectionManager on CollectionManager { // changeLogs.deletedPollIds => changeLogs.deletedMessageIds - lastRequestTsForPollChangeLogs[channel.channelUrl] = now; + lastRequestTsForPollChangeLogs[groupChannel.channelUrl] = now; token = changeLogs.token; } while (changeLogs.hasMore); sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: groupChannel, eventSource: CollectionEventSource.pollChangeLogs, sendingStatus: SendingStatus.succeeded, updatedMessages: updatedMessages, @@ -142,10 +147,10 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// // Messages gap //------------------------------// - void _requestMessagesGap(MessageCollection messageCollection) async { + void _requestMessagesGap(BaseMessageCollection messageCollection) async { final now = DateTime.now().millisecondsSinceEpoch; - final channel = messageCollection.channel; + final channel = messageCollection.baseChannel; int nextEndTs = messageCollection.latestMessage != null ? messageCollection.latestMessage!.createdAt : min(lastRequestTsForMessagesGap[channel.channelUrl] ?? 0, @@ -156,7 +161,7 @@ extension MessageCollectionManager on CollectionManager { ChannelMessagesGapRequest( _chat, channelType: ChannelType.group, - channelUrl: messageCollection.channel.channelUrl, + channelUrl: messageCollection.baseChannel.channelUrl, messageListParams: messageCollection.loadNextParams.toJson(), prevStartTs: nextEndTs, prevEndTs: nextEndTs, @@ -172,14 +177,21 @@ extension MessageCollectionManager on CollectionManager { if (messages == null) { messageCollection.hasNext = true; if (!messageCollection.isDisposed) { - messageCollection.handler.onHugeGapDetected(); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onHugeGapDetected(); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onHugeGapDetected(); + } } } else { messageCollection.hasNext = false; sendEventsToMessageCollection( messageCollection: messageCollection, - groupChannel: channel, + baseChannel: channel, eventSource: CollectionEventSource.messagesGap, sendingStatus: SendingStatus.succeeded, addedMessages: messages, @@ -193,17 +205,25 @@ extension MessageCollectionManager on CollectionManager { //------------------------------// void sendEventsToMessageCollectionList({ required CollectionEventSource eventSource, - List? updatedChannels, + List? updatedChannels, List? deletedChannelUrls, }) { if (updatedChannels != null && updatedChannels.isNotEmpty) { for (final updatedChannel in updatedChannels) { for (final messageCollection in messageCollections) { if (updatedChannel.channelUrl == - messageCollection.channel.channelUrl) { + messageCollection.baseChannel.channelUrl) { if (!messageCollection.isDisposed) { - messageCollection.handler.onChannelUpdated( - GroupChannelContext(eventSource), updatedChannel); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onChannelUpdated(GroupChannelContext(eventSource), + updatedChannel as GroupChannel); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onChannelUpdated(FeedChannelContext(eventSource), + updatedChannel as FeedChannel); + } } break; } @@ -214,10 +234,18 @@ extension MessageCollectionManager on CollectionManager { if (deletedChannelUrls != null && deletedChannelUrls.isNotEmpty) { for (final deletedChannelUrl in deletedChannelUrls) { for (final messageCollection in messageCollections) { - if (deletedChannelUrl == messageCollection.channel.channelUrl) { + if (deletedChannelUrl == messageCollection.baseChannel.channelUrl) { if (!messageCollection.isDisposed) { - messageCollection.handler.onChannelDeleted( - GroupChannelContext(eventSource), deletedChannelUrl); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onChannelDeleted( + GroupChannelContext(eventSource), deletedChannelUrl); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onChannelDeleted( + FeedChannelContext(eventSource), deletedChannelUrl); + } } break; } @@ -230,8 +258,8 @@ extension MessageCollectionManager on CollectionManager { // Send events to message collection //------------------------------// void sendEventsToMessageCollection({ - required MessageCollection messageCollection, - required GroupChannel groupChannel, + required BaseMessageCollection messageCollection, + required BaseChannel baseChannel, required CollectionEventSource eventSource, required SendingStatus sendingStatus, List? addedMessages, @@ -328,31 +356,64 @@ extension MessageCollectionManager on CollectionManager { if (addedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - messageCollection.handler.onMessagesAdded( - MessageContext(eventSource, sendingStatus), - groupChannel, - addedMessagesForEvent, - ); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onMessagesAdded( + MessageContext(eventSource, sendingStatus), + baseChannel as GroupChannel, + addedMessagesForEvent, + ); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onMessagesAdded( + NotificationContext(eventSource, sendingStatus), + baseChannel as FeedChannel, + addedMessagesForEvent, + ); + } } } if (updatedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - messageCollection.handler.onMessagesUpdated( - MessageContext(eventSource, sendingStatus), - groupChannel, - updatedMessagesForEvent, - ); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onMessagesUpdated( + MessageContext(eventSource, sendingStatus), + baseChannel as GroupChannel, + updatedMessagesForEvent, + ); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onMessagesUpdated( + NotificationContext(eventSource, sendingStatus), + baseChannel as FeedChannel, + updatedMessagesForEvent, + ); + } } } if (deletedMessagesForEvent.isNotEmpty) { if (!messageCollection.isDisposed) { - messageCollection.handler.onMessagesDeleted( - MessageContext(eventSource, sendingStatus), - groupChannel, - deletedMessagesForEvent, - ); + if (messageCollection.baseHandler is MessageCollectionHandler) { + (messageCollection.baseHandler as MessageCollectionHandler) + .onMessagesDeleted( + MessageContext(eventSource, sendingStatus), + baseChannel as GroupChannel, + deletedMessagesForEvent, + ); + } else if (messageCollection.baseHandler + is NotificationCollectionHandler) { + (messageCollection.baseHandler as NotificationCollectionHandler) + .onMessagesDeleted( + NotificationContext(eventSource, sendingStatus), + baseChannel as FeedChannel, + deletedMessagesForEvent, + ); + } } } } diff --git a/lib/src/internal/main/chat_manager/command_manager.dart b/lib/src/internal/main/chat_manager/command_manager.dart index 0feb167d..956f617b 100644 --- a/lib/src/internal/main/chat_manager/command_manager.dart +++ b/lib/src/internal/main/chat_manager/command_manager.dart @@ -13,7 +13,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/model/delivery_status.dart'; import 'package:sendbird_chat_sdk/src/internal/main/model/read_status.dart'; import 'package:sendbird_chat_sdk/src/internal/main/model/reconnect_task.dart'; import 'package:sendbird_chat_sdk/src/internal/main/model/typing_status.dart'; -import 'package:sendbird_chat_sdk/src/internal/main/model/unread_count_info.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/model/unread_message_count_info.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/json_converter.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/channel_event.dart'; @@ -23,6 +23,7 @@ import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/message_e import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/session_event.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/user_event.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'; 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'; @@ -32,6 +33,7 @@ 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/define/sendbird_error.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/unread_message_count.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll_update_event.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll_vote_event.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction_event.dart'; @@ -125,10 +127,10 @@ class CommandManager { '\n-[cmd] ${cmd.cmd}\n-[payload] ${jsonEncoder.convert(cmd.payload)}'); } - final unreadCountPayload = cmd.payload['unread_cnt']; - if (unreadCountPayload != null) { - final info = UnreadCountInfo.fromJson(unreadCountPayload); - _updateSubscribedUnreadCountInfo(info); + final unreadMessageCountPayload = cmd.payload['unread_cnt']; + if (unreadMessageCountPayload != null) { + final info = UnreadMessageCountInfo.fromJson(unreadMessageCountPayload); + _updateSubscribedUnreadMessageCountInfo(info); } if (cmd.requestId != null) { @@ -188,16 +190,21 @@ class CommandManager { } } - void _updateSubscribedUnreadCountInfo(UnreadCountInfo? info) { + void _updateSubscribedUnreadMessageCountInfo(UnreadMessageCountInfo? info) { if (info == null) return; - if (_chat.chatContext.unreadCountInfo.ts > info.ts) return; + if (_chat.chatContext.unreadMessageCountInfo.ts > info.ts) return; - final unreadCountInfo = _chat.chatContext.unreadCountInfo; - final didChange = unreadCountInfo.copyWith(info); + final unreadMessageCountInfo = _chat.chatContext.unreadMessageCountInfo; + final didChange = unreadMessageCountInfo.copyWith(info); if (didChange) { - _chat.eventManager.notifyTotalUnreadMessageCountUpdated( - unreadCountInfo.all, - unreadCountInfo.customTypes, + final unreadMessageCount = UnreadMessageCount( + totalCountForGroupChannels: unreadMessageCountInfo.all, + totalCountForFeedChannels: unreadMessageCountInfo.feed, + totalCountByCustomType: unreadMessageCountInfo.customTypes, + ); + + _chat.eventManager.notifyTotalUnreadMessageCountChanged( + unreadMessageCount, ); } } @@ -502,17 +509,29 @@ class CommandManager { final status = ReadStatus.fromJson(cmd.payload); status.saveToCache(_chat); - final channel = await GroupChannel.getChannel(status.channelUrl); - - final isCurrentUser = status.userId == _chat.chatContext.currentUserId; - final hasUnreadCount = - (channel.unreadMessageCount > 0 || channel.unreadMentionCount > 0); - if (isCurrentUser) { - channel.myLastRead = status.timestamp; - if (channel.fromCache) channel.clearUnreadCount(); - if (hasUnreadCount) _chat.eventManager.notifyChannelChanged(channel); - } else { - _chat.eventManager.notifyReadStatusUpdated(channel); + if (cmd.payload['channel_type'] == 'group') { + final channel = await GroupChannel.getChannel(status.channelUrl); + + final isCurrentUser = status.userId == _chat.chatContext.currentUserId; + final hasUnreadCount = + (channel.unreadMessageCount > 0 || channel.unreadMentionCount > 0); + if (isCurrentUser) { + channel.myLastRead = status.timestamp; + if (channel.fromCache) channel.clearUnreadCount(); + if (hasUnreadCount) _chat.eventManager.notifyChannelChanged(channel); + } else { + _chat.eventManager.notifyReadStatusUpdated(channel); + } + } else if (cmd.payload['channel_type'] == 'feed') { + final channel = await FeedChannel.getChannel(status.channelUrl); + + final isCurrentUser = status.userId == _chat.chatContext.currentUserId; + final hasUnreadCount = (channel.unreadMessageCount > 0); + if (isCurrentUser) { + if (hasUnreadCount) _chat.eventManager.notifyChannelChanged(channel); + } else { + _chat.eventManager.notifyReadStatusUpdated(channel); + } } } catch (e) { sbLog.e(StackTrace.current, 'cmd: ${cmd.cmd}, e: $e'); diff --git a/lib/src/internal/main/chat_manager/event_manager.dart b/lib/src/internal/main/chat_manager/event_manager.dart index 42052a06..2ef15f2f 100644 --- a/lib/src/internal/main/chat_manager/event_manager.dart +++ b/lib/src/internal/main/chat_manager/event_manager.dart @@ -3,6 +3,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat_manager/session_manager.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.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'; 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'; @@ -14,6 +15,7 @@ import 'package:sendbird_chat_sdk/src/public/main/handler/channel_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/connection_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/session_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/user_event_handler.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/unread_message_count.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll_update_event.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll_vote_event.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction_event.dart'; @@ -167,6 +169,8 @@ class EventManager { void notifyReactionUpdated(BaseChannel channel, ReactionEvent event) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onReactionUpdated(channel, event); } @@ -176,6 +180,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${restrictedUser.userId}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onUserMuted(channel, restrictedUser); } @@ -185,6 +191,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onUserUnmuted(channel, user); } @@ -194,6 +202,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${restrictedUser.userId}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onUserBanned(channel, restrictedUser); } @@ -203,6 +213,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[userId] ${user.userId}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onUserUnbanned(channel, user); } @@ -211,6 +223,8 @@ class EventManager { void notifyChannelFrozen(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onChannelFrozen(channel); } @@ -219,6 +233,8 @@ class EventManager { void notifyChannelUnfrozen(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onChannelUnfrozen(channel); } @@ -228,6 +244,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[metaData] $data'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { final created = Map.from(data['created'] ?? {}); final updated = Map.from(data['updated'] ?? {}); @@ -244,6 +262,8 @@ class EventManager { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}\n-[metaData] $data'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { final created = Map.from(data['created'] ?? {}); final updated = Map.from(data['updated'] ?? {}); @@ -258,6 +278,8 @@ class EventManager { void notifyOperatorUpdated(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onOperatorUpdated(channel); } @@ -267,20 +289,31 @@ class EventManager { GroupChannel channel, ThreadInfoUpdateEvent event) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); + if (channel is FeedChannel) return; + for (final element in _channelHandlers.values) { element.onThreadInfoUpdated(channel, event); } } // GroupChannelHandler - void notifyReadStatusUpdated(GroupChannel channel) { + void notifyReadStatusUpdated(BaseChannel channel) { sbLog.i(StackTrace.current, '\n-[channelUrl] ${channel.channelUrl}'); - for (final element in _channelHandlers.values) { - if (element is GroupChannelHandler) { - element.onReadStatusUpdated(channel); + if (channel is GroupChannel) { + for (final element in _channelHandlers.values) { + if (element is GroupChannelHandler) { + element.onReadStatusUpdated(channel); + } } } + // else if (channel is FeedChannel) { + // for (final element in _channelHandlers.values) { + // if (element is FeedChannelHandler) { + // element.onReadStatusUpdated(channel); + // } + // } + // } } void notifyDeliveryStatusUpdated(GroupChannel channel) { @@ -489,13 +522,17 @@ class EventManager { } // UserEventHandler - void notifyTotalUnreadMessageCountUpdated( - int totalCount, Map customTypesCount) { + void notifyTotalUnreadMessageCountChanged( + UnreadMessageCount unreadMessageCount) { sbLog.i(StackTrace.current, - '\n-[totalCount] $totalCount\n-[customTypesCount] $customTypesCount'); + '\n-[groupChannelUnreadMessageCount] ${unreadMessageCount.totalCountForGroupChannels}\n-[feedChannelUnreadMessageCount] ${unreadMessageCount.totalCountForFeedChannels}\n-[totalCountByCustomType] ${unreadMessageCount.totalCountByCustomType}'); for (final element in _userHandlers.values) { - element.onTotalUnreadMessageCountUpdated(totalCount, customTypesCount); + element.onTotalUnreadMessageCountChanged(unreadMessageCount); + element.onTotalUnreadMessageCountUpdated( + unreadMessageCount.totalCountForGroupChannels, + unreadMessageCount.totalCountByCustomType, + ); } } diff --git a/lib/src/internal/main/extensions/extensions.dart b/lib/src/internal/main/extensions/extensions.dart index 22c2c43f..052ed795 100644 --- a/lib/src/internal/main/extensions/extensions.dart +++ b/lib/src/internal/main/extensions/extensions.dart @@ -20,6 +20,8 @@ extension ChannelTypeUrlString on ChannelType { return 'group_channels'; case ChannelType.open: return 'open_channels'; + case ChannelType.feed: + return 'group_channels'; // Check } } @@ -29,6 +31,8 @@ extension ChannelTypeUrlString on ChannelType { return 'group'; case ChannelType.open: return 'open'; + case ChannelType.feed: + return 'group'; // Check } } } @@ -44,6 +48,8 @@ extension ChannelListQueryIncludeOptionListToJson contains(ChannelListQueryIncludeOption.includeReadReceipt); final hasDeliveryReceipt = contains(ChannelListQueryIncludeOption.includeDeliveryReceipt); + final hasChatNotification = + contains(ChannelListQueryIncludeOption.includeChatNotification); return { if (hasEmpty) 'show_empty': true, @@ -52,6 +58,17 @@ extension ChannelListQueryIncludeOptionListToJson if (hasMetaData) 'show_metadata': true, if (hasReadReceipt) 'show_read_receipt': true, if (hasDeliveryReceipt) 'show_delivery_receipt': true, + if (hasChatNotification) 'include_chat_notification': true, }; } } + +enum ChannelListQueryIncludeOption { + includeEmpty, + includeMember, + includeFrozen, + includeMetadata, + includeReadReceipt, + includeDeliveryReceipt, + includeChatNotification, +} diff --git a/lib/src/internal/main/model/delivery_status.dart b/lib/src/internal/main/model/delivery_status.dart index a51e9944..3509e2ff 100644 --- a/lib/src/internal/main/model/delivery_status.dart +++ b/lib/src/internal/main/model/delivery_status.dart @@ -31,9 +31,9 @@ class DeliveryStatus implements Cacheable { String get primaryKey => channelUrl; @override - void copyWith(dynamic others) { - if (others is DeliveryStatus) { - updatedDeliveryStatus.addAll(others.updatedDeliveryStatus); + void copyWith(dynamic other) { + if (other is DeliveryStatus) { + updatedDeliveryStatus.addAll(other.updatedDeliveryStatus); } } } diff --git a/lib/src/internal/main/model/read_status.dart b/lib/src/internal/main/model/read_status.dart index 1c7e0105..1207d2a8 100644 --- a/lib/src/internal/main/model/read_status.dart +++ b/lib/src/internal/main/model/read_status.dart @@ -45,7 +45,7 @@ class ReadStatus implements Cacheable { String get key => userId; @override - void copyWith(dynamic others) { - timestamp = others.timestamp; + void copyWith(dynamic other) { + timestamp = other.timestamp; } } diff --git a/lib/src/internal/main/model/read_status.g.dart b/lib/src/internal/main/model/read_status.g.dart index 75cbc17a..7b00eb1c 100644 --- a/lib/src/internal/main/model/read_status.g.dart +++ b/lib/src/internal/main/model/read_status.g.dart @@ -16,4 +16,5 @@ ReadStatus _$ReadStatusFromJson(Map json) => ReadStatus( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; diff --git a/lib/src/internal/main/model/typing_status.dart b/lib/src/internal/main/model/typing_status.dart index 53dedb9c..42dd5b6d 100644 --- a/lib/src/internal/main/model/typing_status.dart +++ b/lib/src/internal/main/model/typing_status.dart @@ -28,10 +28,10 @@ class TypingStatus implements Cacheable { String get key => user.userId; @override - void copyWith(dynamic others) { - channelType = others.channelType; - channelUrl = others.urlKeyword; - user = others.user; - timestamp = others.timestamp; + void copyWith(dynamic other) { + channelType = other.channelType; + channelUrl = other.urlKeyword; + user = other.user; + timestamp = other.timestamp; } } diff --git a/lib/src/internal/main/model/unread_count_info.dart b/lib/src/internal/main/model/unread_message_count_info.dart similarity index 62% rename from lib/src/internal/main/model/unread_count_info.dart rename to lib/src/internal/main/model/unread_message_count_info.dart index 9783ce6b..b4e3cf61 100644 --- a/lib/src/internal/main/model/unread_count_info.dart +++ b/lib/src/internal/main/model/unread_message_count_info.dart @@ -2,23 +2,34 @@ import 'package:json_annotation/json_annotation.dart'; -part 'unread_count_info.g.dart'; +part 'unread_message_count_info.g.dart'; @JsonSerializable(createToJson: false) -class UnreadCountInfo { +class UnreadMessageCountInfo { int all; + int feed; Map customTypes; int ts; - UnreadCountInfo({ + UnreadMessageCountInfo({ this.all = 0, + this.feed = 0, this.customTypes = const {}, this.ts = 0, }); - bool copyWith(UnreadCountInfo others) { + bool copyWith(UnreadMessageCountInfo others) { var didChange = false; - ts = others.ts; + + if (all != others.all) { + didChange = true; + all = others.all; + } + + if (feed != others.feed) { + didChange = true; + feed = others.feed; + } others.customTypes.forEach((key, value) { final currValue = customTypes[key]; @@ -28,13 +39,10 @@ class UnreadCountInfo { } }); - if (all != others.all) { - didChange = true; - all = others.all; - } + ts = others.ts; return didChange; } - static UnreadCountInfo fromJson(Map json) => - _$UnreadCountInfoFromJson(json); + static UnreadMessageCountInfo fromJson(Map json) => + _$UnreadMessageCountInfoFromJson(json); } diff --git a/lib/src/internal/main/model/unread_count_info.g.dart b/lib/src/internal/main/model/unread_message_count_info.g.dart similarity index 69% rename from lib/src/internal/main/model/unread_count_info.g.dart rename to lib/src/internal/main/model/unread_message_count_info.g.dart index 807b53f3..3b72af32 100644 --- a/lib/src/internal/main/model/unread_count_info.g.dart +++ b/lib/src/internal/main/model/unread_message_count_info.g.dart @@ -1,14 +1,16 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'unread_count_info.dart'; +part of 'unread_message_count_info.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -UnreadCountInfo _$UnreadCountInfoFromJson(Map json) => - UnreadCountInfo( +UnreadMessageCountInfo _$UnreadMessageCountInfoFromJson( + Map json) => + UnreadMessageCountInfo( all: json['all'] as int? ?? 0, + feed: json['feed'] as int? ?? 0, customTypes: (json['custom_types'] as Map?)?.map( (k, e) => MapEntry(k, e as int), ) ?? diff --git a/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_change_logs_request.dart b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_change_logs_request.dart new file mode 100644 index 00000000..443c2996 --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_change_logs_request.dart @@ -0,0 +1,51 @@ +// 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/main/chat_cache/cache_service.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_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/main/model/channel/feed_channel_change_logs.dart'; +import 'package:sendbird_chat_sdk/src/public/main/params/channel/feed_channel_change_logs_params.dart'; + +class FeedChannelChangeLogsGetRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + FeedChannelChangeLogsGetRequest( + Chat chat, + FeedChannelChangeLogsParams params, { + String? token, + int? timestamp, + String? userId, + }) : super(chat: chat) { + url = + 'users/${userId ?? chat.chatContext.currentUserId}/my_group_channels/changelogs'; + queryParams = params.toJson(); + queryParams['show_member'] = true; + queryParams['show_read_receipt'] = true; + queryParams['show_delivery_receipt'] = true; + queryParams['is_feed_channel'] = true; + + if (timestamp != null && timestamp > 0) { + queryParams['change_ts'] = timestamp; + } else if (token != null) { + queryParams['token'] = token; + } + } + + @override + Future response(Map res) async { + final response = FeedChannelChangeLogs.fromJsonWithChat(chat, res); + for (var index = 0; index < response.updatedChannels.length; index++) { + final channel = response.updatedChannels[index]; + channel.saveToCache(chat); + (res['updated'][index] as Map) + .cacheMetaData(channel: channel, ts: res['ts']); + (res['updated'][index] as Map).cacheReadStatus(channel); + (res['updated'][index] as Map) + .cacheDeliveryStatus(channel); + } + return response; + } +} diff --git a/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_list_request.dart b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_list_request.dart new file mode 100644 index 00000000..8b27ee08 --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_list_request.dart @@ -0,0 +1,50 @@ +// 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/main/chat_cache/cache_service.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_extensions.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/internal/network/http/http_client/response/responses.dart'; + +class FeedChannelListRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + FeedChannelListRequest( + Chat chat, { + required int limit, + List options = const [], + String? token, + }) : super(chat: chat) { + url = 'users/${userId ?? chat.chatContext.currentUserId}/my_group_channels'; + + queryParams = { + 'limit': limit, + if (token != null) 'token': token, + }; + + queryParams.addAll(options.toJson()); + queryParams['is_feed_channel'] = true; + queryParams['order'] = 'latest_last_message'; + + queryParams.removeWhere((key, value) => value == null); + } + + @override + Future response( + Map res) async { + final response = FeedChannelListQueryResponse.fromJsonWithChat(chat, res); + for (var index = 0; index < response.channels.length; index++) { + final channel = response.channels[index]; + channel.saveToCache(chat); + (res['channels'][index] as Map) + .cacheMetaData(channel: channel, ts: res['ts']); + (res['channels'][index] as Map).cacheReadStatus(channel); + (res['channels'][index] as Map) + .cacheDeliveryStatus(channel); + } + return response; + } +} diff --git a/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_refresh_request.dart b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_refresh_request.dart new file mode 100644 index 00000000..5d9439ef --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_refresh_request.dart @@ -0,0 +1,38 @@ +// 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/main/chat_cache/cache_service.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_extensions.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/channel/feed_channel/feed_channel.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; + +class FeedChannelRefreshRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + FeedChannelRefreshRequest( + Chat chat, + String channelUrl, { + List options = const [], + bool passive = false, + }) : super(chat: chat) { + url = '${passive ? '/sdk/' : ''}group_channels/$channelUrl'; + queryParams = options.toJson(); + queryParams['is_feed_channel'] = true; + } + + @override + Future response(Map res) async { + final channel = FeedChannel( + groupChannel: GroupChannel.fromJsonWithChat(chat, res)..saveToCache(chat), + )..set(SendbirdChat().chat); // Set the singleton chat + res.cacheMetaData(channel: channel); + res.cacheReadStatus(channel); + res.cacheDeliveryStatus(channel); + return channel; + } +} diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_log_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_logs_request.dart similarity index 97% rename from lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_log_request.dart rename to lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_logs_request.dart index 401d7461..6ace61c1 100644 --- a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_log_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_change_logs_request.dart @@ -31,6 +31,8 @@ class GroupChannelChangeLogsGetRequest extends ApiRequest { } else if (token != null) { queryParams['token'] = token; } + + queryParams['is_explicit_request'] = true; } @override diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_list_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_list_request.dart index e0346228..2d341d05 100644 --- a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_list_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_list_request.dart @@ -1,15 +1,15 @@ // 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/main/chat_cache/cache_service.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_extensions.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/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/http/http_client/response/responses.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/group_channel/group_channel.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/channel/group_channel_filter.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/response/responses.dart'; class GroupChannelListRequest extends ApiRequest { @override @@ -66,6 +66,8 @@ class GroupChannelListRequest extends ApiRequest { queryParams['metadata_order_key'] = filter.metadataOrderKey; } + queryParams['is_explicit_request'] = true; + queryParams.removeWhere((key, value) => value == null); } diff --git a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_refresh_request.dart b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_refresh_request.dart index f030cd54..18654dca 100644 --- a/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_refresh_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/group_channel/group_channel_refresh_request.dart @@ -1,13 +1,12 @@ // 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/main/chat_cache/cache_service.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_extensions.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/public/core/channel/group_channel/group_channel.dart'; -import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; -import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.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/channel/group_channel/group_channel.dart'; class GroupChannelRefreshRequest extends ApiRequest { @override diff --git a/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_list_request.dart b/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_list_request.dart index 1fa39881..1745b824 100644 --- a/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_list_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_list_request.dart @@ -1,14 +1,13 @@ // 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/main/chat_cache/cache_service.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat_cache/channel/channel_cache_extensions.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/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/http/http_client/response/responses.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/open_channel/open_channel.dart'; -import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; class OpenChannelListRequest extends ApiRequest { @override diff --git a/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_refresh_request.dart b/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_refresh_request.dart index 3ebf7342..2b89b7a8 100644 --- a/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_refresh_request.dart +++ b/lib/src/internal/network/http/http_client/request/channel/open_channel/open_channel_refresh_request.dart @@ -7,7 +7,6 @@ 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/channel/open_channel/open_channel.dart'; -import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; class OpenChannelRefreshRequest extends ApiRequest { @override diff --git a/lib/src/internal/network/http/http_client/request/main/notifications/global_notification_channel_setting_get_request.dart b/lib/src/internal/network/http/http_client/request/main/notifications/global_notification_channel_setting_get_request.dart new file mode 100644 index 00000000..c5076b7e --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/main/notifications/global_notification_channel_setting_get_request.dart @@ -0,0 +1,24 @@ +// 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'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/global_notification_channel_setting.dart'; + +class GlobalNotificationChannelSettingGetRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + GlobalNotificationChannelSettingGetRequest( + Chat chat, + ) : super(chat: chat) { + url = 'notifications/settings'; + } + + @override + Future response( + Map res, + ) async { + return GlobalNotificationChannelSetting(setting: res); + } +} diff --git a/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_get_request.dart b/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_get_request.dart new file mode 100644 index 00000000..768e3c6b --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_get_request.dart @@ -0,0 +1,23 @@ +// 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'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/notification_template.dart'; + +class NotificationTemplateGetRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + NotificationTemplateGetRequest( + Chat chat, { + required String key, + }) : super(chat: chat) { + url = 'notifications/templates/$key'; + } + + @override + Future response(Map res) async { + return NotificationTemplate(template: res); + } +} diff --git a/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_list_get_request.dart b/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_list_get_request.dart new file mode 100644 index 00000000..3ac3bf28 --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/main/notifications/notification_template_list_get_request.dart @@ -0,0 +1,44 @@ +// 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'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/notification_template_list.dart'; +import 'package:sendbird_chat_sdk/src/public/main/params/notifications/notification_template_list_params.dart'; + +class NotificationTemplateListGetRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.get; + + NotificationTemplateListGetRequest( + Chat chat, + NotificationTemplateListParams params, { + String? token, + }) : super(chat: chat) { + url = 'notifications/templates'; + queryParams['reverse'] = params.reverse; + queryParams['limit'] = params.limit; + queryParams['show_ui_template'] = true; + queryParams['show_color_variables'] = true; + queryParams['order'] = 'updated_at'; + + if (params.keys != null && params.keys!.isNotEmpty) { + queryParams['keys'] = params.keys; + } + + if (token != null && token.isNotEmpty) { + queryParams['token'] = token; + } + } + + @override + Future response( + Map res, + ) async { + return NotificationTemplateList( + templateList: res, + hasMore: res['has_more'], + token: res['next'], + ); + } +} diff --git a/lib/src/internal/network/http/http_client/request/user/count/user_unread_message_count_request.dart b/lib/src/internal/network/http/http_client/request/user/count/user_unread_message_count_request.dart index 2028649f..3d35aa6e 100644 --- a/lib/src/internal/network/http/http_client/request/user/count/user_unread_message_count_request.dart +++ b/lib/src/internal/network/http/http_client/request/user/count/user_unread_message_count_request.dart @@ -5,6 +5,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/utils/enum_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'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/unread_message_count.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/group_channel_total_unread_message_count_params.dart'; class UserTotalUnreadMessageCountGetRequest extends ApiRequest { @@ -23,11 +24,20 @@ class UserTotalUnreadMessageCountGetRequest extends ApiRequest { queryParams = { if (customTypes.isNotEmpty) 'custom_types': customTypes, 'super_mode': groupChannelSuperFilterEnum(superFilter), + if (chat.chatContext.appInfo?.notificationInfo?.feedChannels.isNotEmpty ?? + false) + 'include_feed_channel': true, }; } @override - Future response(Map res) async { - return res['unread_count']; + Future response(Map res) async { + final int groupChannelUnreadMessageCount = res['unread_count'] ?? 0; + final int feedChannelUnreadMessageCount = res['unread_feed_count'] ?? 0; + return UnreadMessageCount( + totalCountForGroupChannels: groupChannelUnreadMessageCount, + totalCountForFeedChannels: feedChannelUnreadMessageCount, + totalCountByCustomType: {}, + ); } } 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 e009e567..830bace6 100644 --- a/lib/src/internal/network/http/http_client/response/responses.dart +++ b/lib/src/internal/network/http/http_client/response/responses.dart @@ -3,12 +3,14 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.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'; 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/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'; +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll.dart'; part 'responses.g.dart'; @@ -104,6 +106,29 @@ class ChannelListQueryResponse { } } +@JsonSerializable(createToJson: false) +class FeedChannelListQueryResponse { + @JsonKey(name: 'channels') + @FeedChannelConverter() + List channels; + + String? next; + + FeedChannelListQueryResponse({ + this.channels = const [], + this.next, + }); + + factory FeedChannelListQueryResponse.fromJsonWithChat( + Chat chat, Map json) { + final res = _$FeedChannelListQueryResponseFromJson(json); + for (var element in res.channels) { + element.set(chat); + } + return res; + } +} + @JsonSerializable(createToJson: false) class MessageSearchQueryResponse { List results; @@ -198,6 +223,25 @@ class _ChannelConverter implements JsonConverter { } } +class FeedChannelConverter implements JsonConverter { + const FeedChannelConverter(); + + @override + FeedChannel fromJson(Object json) { + if (json is Map) { + return FeedChannel( + groupChannel: GroupChannel.fromJson(json), + )..set(SendbirdChat().chat); // Set the singleton chat; + } + return json as FeedChannel; + } + + @override + Object toJson(FeedChannel object) { + return object; + } +} + class _UserConverter implements JsonConverter { const _UserConverter(); 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 89ca9bcc..eb7624f2 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 @@ -58,6 +58,16 @@ ChannelListQueryResponse next: json['next'] as String?, ); +FeedChannelListQueryResponse _$FeedChannelListQueryResponseFromJson( + Map json) => + FeedChannelListQueryResponse( + channels: (json['channels'] as List?) + ?.map((e) => const FeedChannelConverter().fromJson(e as Object)) + .toList() ?? + const [], + next: json['next'] as String?, + ); + MessageSearchQueryResponse _$MessageSearchQueryResponseFromJson( Map json) => MessageSearchQueryResponse( diff --git a/lib/src/internal/network/websocket/event/channel_event.g.dart b/lib/src/internal/network/websocket/event/channel_event.g.dart index e1dcbf77..ece829f1 100644 --- a/lib/src/internal/network/websocket/event/channel_event.g.dart +++ b/lib/src/internal/network/websocket/event/channel_event.g.dart @@ -18,4 +18,5 @@ ChannelEvent _$ChannelEventFromJson(Map json) => ChannelEvent( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; diff --git a/lib/src/internal/network/websocket/event/message_event.g.dart b/lib/src/internal/network/websocket/event/message_event.g.dart index 8d621a1f..3b87ff83 100644 --- a/lib/src/internal/network/websocket/event/message_event.g.dart +++ b/lib/src/internal/network/websocket/event/message_event.g.dart @@ -33,6 +33,7 @@ MessageEvent _$MessageEventFromJson(Map json) => MessageEvent( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$MentionTypeEnumMap = { 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 139d0627..dd98226c 100644 --- a/lib/src/public/core/channel/base_channel/base_channel.dart +++ b/lib/src/public/core/channel/base_channel/base_channel.dart @@ -1,6 +1,7 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'dart:async'; +import 'dart:io'; import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; @@ -38,6 +39,7 @@ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/ import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/report/user_report_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/response/responses.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/command/command.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'; 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'; @@ -76,24 +78,69 @@ abstract class BaseChannel implements Cacheable { /// The topic or name of the channel. String name; - /// The cover image URL. - String coverUrl; - /// The creation time of the channel. int? createdAt; + /// The cover image URL. + String get coverUrl { + checkUnsupportedAction(); + return _coverUrl; + } + + set coverUrl(value) { + checkUnsupportedAction(); + _coverUrl = value; + } + /// The channel data. - String data; + String get data { + checkUnsupportedAction(); + return _data; + } /// The custom type of the channel. - String customType; + String get customType { + checkUnsupportedAction(); + return _customType; + } + + set customType(value) { + checkUnsupportedAction(); + _customType = value; + } /// Whether the channel is frozen. @JsonKey(name: 'freeze') - bool isFrozen; + bool get isFrozen { + checkUnsupportedAction(); + return _isFrozen; + } + + set isFrozen(value) { + checkUnsupportedAction(); + _isFrozen = value; + } /// Whether the channel is ephemeral. - bool isEphemeral; + bool get isEphemeral { + checkUnsupportedAction(); + return _isEphemeral; + } + + @JsonKey(includeFromJson: false, includeToJson: false) + String _coverUrl; + + @JsonKey(includeFromJson: false, includeToJson: false) + String _data; + + @JsonKey(includeFromJson: false, includeToJson: false) + String _customType; + + @JsonKey(includeFromJson: false, includeToJson: false) + bool _isFrozen; + + @JsonKey(includeFromJson: false, includeToJson: false) + bool _isEphemeral; @JsonKey(includeFromJson: false, includeToJson: false) bool fromCache = false; @@ -107,20 +154,27 @@ abstract class BaseChannel implements Cacheable { BaseChannel({ required this.channelUrl, - this.createdAt, this.name = '', - this.coverUrl = '', - this.data = '', - this.customType = '', - this.isFrozen = false, - this.isEphemeral = false, + this.createdAt, + coverUrl = '', + data = '', + customType = '', + isFrozen = false, + isEphemeral = false, this.fromCache = false, this.dirty = false, - }); + }) : _coverUrl = coverUrl, + _data = data, + _customType = customType, + _isFrozen = isFrozen, + _isEphemeral = isEphemeral; /// ChannelType - ChannelType get channelType => - this is GroupChannel ? ChannelType.group : ChannelType.open; + ChannelType get channelType => this is GroupChannel + ? ChannelType.group + : this is OpenChannel + ? ChannelType.open + : ChannelType.feed; void set(Chat chat) { this.chat = chat; @@ -139,18 +193,27 @@ abstract class BaseChannel implements Cacheable { element.set(chat); } } + + if (this is FeedChannel) { + (this as FeedChannel).lastMessage?.set(chat); + for (final element in (this as FeedChannel).members) { + element.set(chat); + } + } } static Future getBaseChannel( - ChannelType type, + ChannelType channelType, String channelUrl, { Chat? chat, }) async { - switch (type) { + switch (channelType) { case ChannelType.group: return GroupChannel.getChannel(channelUrl, chat: chat); case ChannelType.open: return OpenChannel.getChannel(channelUrl, chat: chat); + case ChannelType.feed: + return FeedChannel.getChannel(channelUrl, chat: chat); } } @@ -159,10 +222,19 @@ abstract class BaseChannel implements Cacheable { String channelUrl, { Chat? chat, }) async { - if (channelType == ChannelType.group) { - return GroupChannel.refresh(channelUrl, chat: chat); - } else { - return OpenChannel.refresh(channelUrl, chat: chat); + switch (channelType) { + case ChannelType.group: + return GroupChannel.refresh(channelUrl, chat: chat); + case ChannelType.open: + return OpenChannel.refresh(channelUrl, chat: chat); + case ChannelType.feed: + return FeedChannel.refresh(channelUrl, chat: chat); + } + } + + void checkUnsupportedAction() { + if (channelType == ChannelType.feed) { + throw NotSupportedException(); } } @@ -200,19 +272,19 @@ abstract class BaseChannel implements Cacheable { String get primaryKey => channelUrl; @override - void copyWith(others) { - if (others is! BaseChannel) return; - - channelUrl = others.channelUrl; - name = others.name; - coverUrl = others.coverUrl; - createdAt = others.createdAt; - data = others.data; - customType = others.customType; - isFrozen = others.isFrozen; - isEphemeral = others.isEphemeral; - - fromCache = others.fromCache; - dirty = others.dirty; + void copyWith(dynamic other) { + if (other is! BaseChannel) return; + + channelUrl = other.channelUrl; + name = other.name; + createdAt = other.createdAt; + _coverUrl = other.coverUrl; + _data = other.data; + _customType = other.customType; + _isFrozen = other.isFrozen; + _isEphemeral = other.isEphemeral; + + fromCache = other.fromCache; + dirty = other.dirty; } } 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 460ef805..c7277810 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 @@ -34,6 +34,7 @@ extension BaseChannelMessage on BaseChannel { UserMessageHandler? handler, }) { sbLog.i(StackTrace.current, 'text: $text'); + checkUnsupportedAction(); final params = UserMessageCreateParams(message: text); return sendUserMessage( @@ -48,6 +49,7 @@ extension BaseChannelMessage on BaseChannel { UserMessageHandler? handler, }) { sbLog.i(StackTrace.current, 'message: ${params.message}'); + checkUnsupportedAction(); if (params.message.isEmpty) { throw InvalidParameterException(); @@ -79,6 +81,7 @@ extension BaseChannelMessage on BaseChannel { pendingUserMessage.sendingStatus = SendingStatus.pending; pendingUserMessage.sender = Sender.fromUser(chat.chatContext.currentUser, this); + pendingUserMessage.messageCreateParams = params; runZonedGuarded(() { chat.commandManager.sendCommand(cmd).then((result) { @@ -113,7 +116,8 @@ extension BaseChannelMessage on BaseChannel { UserMessage message, { UserMessageHandler? handler, }) { - sbLog.i(StackTrace.current, 'message: $message'); + sbLog.i(StackTrace.current, 'message.requestId: ${message.requestId}'); + checkUnsupportedAction(); if (message.sendingStatus != SendingStatus.failed) { throw InvalidParameterException(); @@ -125,8 +129,8 @@ extension BaseChannelMessage on BaseChannel { throw InvalidParameterException(); } - final params = - UserMessageCreateParams.withMessage(message, deepCopy: false); + final params = message.messageCreateParams ?? + UserMessageCreateParams.withMessage(message); return sendUserMessage( params, handler: handler, @@ -137,6 +141,7 @@ extension BaseChannelMessage on BaseChannel { Future updateUserMessage( int messageId, UserMessageUpdateParams params) async { sbLog.i(StackTrace.current, 'message: ${params.message}'); + checkUnsupportedAction(); if (messageId <= 0) { throw InvalidParameterException(); @@ -169,6 +174,7 @@ extension BaseChannelMessage on BaseChannel { }) { sbLog.i(StackTrace.current, 'params.uploadFile.name: ${params.fileInfo.fileName}'); + checkUnsupportedAction(); if (params.fileInfo.hasSource == false) { throw InvalidParameterException(); @@ -180,6 +186,7 @@ extension BaseChannelMessage on BaseChannel { pendingFileMessage.sendingStatus = SendingStatus.pending; pendingFileMessage.sender = Sender.fromUser(chat.chatContext.currentUser, this); + pendingFileMessage.messageCreateParams = params; bool isCanceled = false; runZonedGuarded(() async { @@ -297,6 +304,7 @@ extension BaseChannelMessage on BaseChannel { /// Cancels an ongoing `FileMessage` upload. bool cancelFileMessageUpload(String requestId) { sbLog.i(StackTrace.current, 'requestId: $requestId'); + checkUnsupportedAction(); if (requestId.isEmpty) { throw InvalidParameterException(); @@ -313,14 +321,16 @@ extension BaseChannelMessage on BaseChannel { } /// Resends a file with given file information. + /// [fileMessage] Failed fileMessage. + /// [file] File to resend. If there is a fileUrl or a fileBytes in fileMessage, this will be ignored. FileMessage resendFileMessage( FileMessage message, { - required FileMessageCreateParams params, + File? file, FileMessageHandler? handler, ProgressHandler? progressHandler, }) { - sbLog.i(StackTrace.current, - 'params.uploadFile.name: ${params.fileInfo.fileName}'); + sbLog.i(StackTrace.current, 'message.requestId: ${message.requestId}'); + checkUnsupportedAction(); if (message.sendingStatus != SendingStatus.failed) { throw InvalidParameterException(); @@ -332,6 +342,16 @@ extension BaseChannelMessage on BaseChannel { throw InvalidParameterException(); } + if (message.messageCreateParams != null) { + if (message.messageCreateParams?.fileInfo.fileUrl == null && + message.messageCreateParams?.fileInfo.fileBytes == null && + file != null) { + message.messageCreateParams?.fileInfo.file = file; + } + } + + final params = message.messageCreateParams ?? + FileMessageCreateParams.withMessage(message); return sendFileMessage( params, progressHandler: progressHandler, @@ -346,6 +366,7 @@ extension BaseChannelMessage on BaseChannel { FileMessageUpdateParams params, ) async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); if (messageId <= 0) { throw InvalidParameterException(); @@ -373,6 +394,7 @@ extension BaseChannelMessage on BaseChannel { /// Deletes a message. Future deleteMessage(int messageId) async { sbLog.i(StackTrace.current, 'messageId: $messageId'); + checkUnsupportedAction(); if (messageId <= 0) { throw InvalidParameterException(); @@ -392,6 +414,7 @@ extension BaseChannelMessage on BaseChannel { List targetLanguages, ) async { sbLog.i(StackTrace.current, 'message: ${message.message}'); + checkUnsupportedAction(); if (message.messageId <= 0) { throw InvalidParameterException(); @@ -418,6 +441,7 @@ extension BaseChannelMessage on BaseChannel { BaseMessageHandler? handler, }) { sbLog.i(StackTrace.current, 'message: ${message.message}'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); @@ -427,8 +451,7 @@ extension BaseChannelMessage on BaseChannel { message.extendedMessage.clear(); if (message is UserMessage) { - final params = - UserMessageCreateParams.withMessage(message, deepCopy: false); + final params = UserMessageCreateParams.withMessage(message); if (params.pollId != null) { throw InvalidParameterException(); } @@ -437,8 +460,7 @@ extension BaseChannelMessage on BaseChannel { handler: handler, ); } else if (message is FileMessage) { - final params = - FileMessageCreateParams.withMessage(message, deepCopy: false); + final params = FileMessageCreateParams.withMessage(message); return targetChannel.sendFileMessage( params, handler: handler, @@ -456,6 +478,7 @@ extension BaseChannelMessage on BaseChannel { MessageListParams params, ) async { sbLog.i(StackTrace.current, 'timestamp: $timestamp'); + checkUnsupportedAction(); if (timestamp <= 0) { throw InvalidParameterException(); @@ -483,6 +506,7 @@ extension BaseChannelMessage on BaseChannel { MessageListParams params, ) async { sbLog.i(StackTrace.current, 'messageId: $messageId'); + checkUnsupportedAction(); if (messageId <= 0) { throw InvalidParameterException(); @@ -515,6 +539,7 @@ extension BaseChannelMessage on BaseChannel { String? token, }) async { sbLog.i(StackTrace.current, 'timestamp: $timestamp'); + checkUnsupportedAction(); return await chat.apiClient.send( ChannelMessageChangeLogGetRequest( 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 3a552a4e..d749dc14 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 @@ -10,6 +10,7 @@ extension BaseChannelMessageMetaArray on BaseChannel { List keys, ) async { sbLog.i(StackTrace.current, 'keys: $keys'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); @@ -39,6 +40,7 @@ extension BaseChannelMessageMetaArray on BaseChannel { List keys, ) async { sbLog.i(StackTrace.current, 'keys: $keys'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); @@ -68,6 +70,7 @@ extension BaseChannelMessageMetaArray on BaseChannel { List metaArrays, ) async { sbLog.i(StackTrace.current, 'metaArrays: $metaArrays'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); @@ -95,6 +98,7 @@ extension BaseChannelMessageMetaArray on BaseChannel { List metaArrays, ) async { sbLog.i(StackTrace.current, 'metaArrays: $metaArrays'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); diff --git a/lib/src/public/core/channel/base_channel/base_channel_meta_counters.dart b/lib/src/public/core/channel/base_channel/base_channel_meta_counters.dart index 6e4f7718..bcc01ea9 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_meta_counters.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_meta_counters.dart @@ -8,6 +8,7 @@ extension BaseChannelMetaCounters on BaseChannel { Future> createMetaCounters( Map metaCounters) async { sbLog.i(StackTrace.current, 'metaCounters: $metaCounters'); + checkUnsupportedAction(); if (metaCounters.isEmpty) { throw InvalidParameterException(); @@ -26,6 +27,7 @@ extension BaseChannelMetaCounters on BaseChannel { /// Gets meta counters. Future> getMetaCounters(List keys) async { sbLog.i(StackTrace.current, 'keys: $keys'); + checkUnsupportedAction(); if (keys.isEmpty) { throw InvalidParameterException(); @@ -44,6 +46,7 @@ extension BaseChannelMetaCounters on BaseChannel { /// Get all meta counters. Future> getAllMetaCounters() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); return await chat.apiClient.send( ChannelMetaCounterGetRequest( @@ -58,6 +61,7 @@ extension BaseChannelMetaCounters on BaseChannel { Future> updateMetaCounters( Map metaCounters) async { sbLog.i(StackTrace.current, 'metaCounters: $metaCounters'); + checkUnsupportedAction(); if (metaCounters.isEmpty) { throw InvalidParameterException(); @@ -79,6 +83,7 @@ extension BaseChannelMetaCounters on BaseChannel { Future> increaseMetaCounters( Map metaCounters) async { sbLog.i(StackTrace.current, 'metaCounters: $metaCounters'); + checkUnsupportedAction(); if (metaCounters.isEmpty) { throw InvalidParameterException(); @@ -100,6 +105,7 @@ extension BaseChannelMetaCounters on BaseChannel { Future> decreaseMetaCounters( Map metaCounters) async { sbLog.i(StackTrace.current, 'metaCounters: $metaCounters'); + checkUnsupportedAction(); if (metaCounters.isEmpty) { throw InvalidParameterException(); @@ -119,6 +125,7 @@ extension BaseChannelMetaCounters on BaseChannel { /// Deletes a meta counter. Future deleteMetaCounters(String key) async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); if (key.isEmpty) { throw InvalidParameterException(); @@ -137,6 +144,7 @@ extension BaseChannelMetaCounters on BaseChannel { /// Deletes all meta counters. Future deleteAllMetaCounters() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); return await chat.apiClient.send( ChannelMetaCounterDeleteAllRequest( diff --git a/lib/src/public/core/channel/base_channel/base_channel_meta_data.dart b/lib/src/public/core/channel/base_channel/base_channel_meta_data.dart index 80d872bc..dd885bf5 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_meta_data.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_meta_data.dart @@ -9,6 +9,7 @@ extension BaseChannelMetaData on BaseChannel { Map metaData, ) async { sbLog.i(StackTrace.current, 'metaData: $metaData'); + checkUnsupportedAction(); if (metaData.isEmpty) { throw InvalidParameterException(); @@ -39,6 +40,7 @@ extension BaseChannelMetaData on BaseChannel { /// Gets meta data. Future> getMetaData(List keys) async { sbLog.i(StackTrace.current, 'keys: $keys'); + checkUnsupportedAction(); if (keys.isEmpty) { throw InvalidParameterException(); @@ -69,6 +71,7 @@ extension BaseChannelMetaData on BaseChannel { /// Gets all meta data. Future> getAllMetaData() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); final result = await chat.apiClient.send( ChannelMetaDataGetRequest( @@ -95,6 +98,7 @@ extension BaseChannelMetaData on BaseChannel { Future> updateMetaData( Map metaData) async { sbLog.i(StackTrace.current, 'metaData: $metaData'); + checkUnsupportedAction(); if (metaData.isEmpty) { throw InvalidParameterException(); @@ -125,6 +129,7 @@ extension BaseChannelMetaData on BaseChannel { /// Deletes a meta data. Future deleteMetaData(String key) async { sbLog.i(StackTrace.current, 'key: $key'); + checkUnsupportedAction(); if (key.isEmpty) { throw InvalidParameterException(); @@ -153,6 +158,7 @@ extension BaseChannelMetaData on BaseChannel { /// Deletes all meta data. Future deleteAllMetaData() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); final ts = await chat.apiClient.send( ChannelMetaDataDeleteAllRequest( @@ -189,6 +195,7 @@ extension BaseChannelMetaData on BaseChannel { /// by this method may not contain all metadata mappings. Map getCachedMetaData() { sbLog.i(StackTrace.current); + checkUnsupportedAction(); final metaData = chat.channelCache .find(channelKey: channelUrl) diff --git a/lib/src/public/core/channel/base_channel/base_channel_moderation.dart b/lib/src/public/core/channel/base_channel/base_channel_moderation.dart index 628916d0..82379e9a 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_moderation.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_moderation.dart @@ -7,6 +7,7 @@ extension BaseChannelModeration on BaseChannel { /// Gets my muted information in this channel. Future getMyMuteInfo() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); return await chat.apiClient.send( ChannelMyMuteInfoGetRequest( @@ -27,6 +28,7 @@ extension BaseChannelModeration on BaseChannel { String? description, }) async { sbLog.i(StackTrace.current, 'userId: $userId'); + checkUnsupportedAction(); if (userId.isEmpty) { throw InvalidParameterException(); @@ -46,6 +48,7 @@ extension BaseChannelModeration on BaseChannel { /// Operators can unban `User` who has been banned from this channel. Future unbanUser({required String userId}) async { sbLog.i(StackTrace.current, 'userId: $userId'); + checkUnsupportedAction(); if (userId.isEmpty) { throw InvalidParameterException(); @@ -67,6 +70,7 @@ extension BaseChannelModeration on BaseChannel { String? description, }) async { sbLog.i(StackTrace.current, 'userId: $userId'); + checkUnsupportedAction(); if (userId.isEmpty) { throw InvalidParameterException(); @@ -86,6 +90,7 @@ extension BaseChannelModeration on BaseChannel { /// Unmuted `User`'s messages are again shown to current `User`. Future unmuteUser({required String userId}) async { sbLog.i(StackTrace.current, 'userId: $userId'); + checkUnsupportedAction(); if (userId.isEmpty) { throw InvalidParameterException(); @@ -105,6 +110,7 @@ extension BaseChannelModeration on BaseChannel { String? description, }) async { sbLog.i(StackTrace.current, 'category: $category'); + checkUnsupportedAction(); await chat.apiClient.send(ChannelReportRequest( chat, @@ -123,6 +129,7 @@ extension BaseChannelModeration on BaseChannel { }) async { sbLog.i( StackTrace.current, 'message: ${message.message}, category: $category'); + checkUnsupportedAction(); final senderId = message.sender?.userId; if (senderId == null || senderId.isEmpty) { @@ -147,6 +154,7 @@ extension BaseChannelModeration on BaseChannel { String? description, }) async { sbLog.i(StackTrace.current, 'userId: $userId, category: $category'); + checkUnsupportedAction(); await chat.apiClient.send(UserReportRequest( chat, diff --git a/lib/src/public/core/channel/base_channel/base_channel_operator.dart b/lib/src/public/core/channel/base_channel/base_channel_operator.dart index cca65c30..652d7139 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_operator.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_operator.dart @@ -8,6 +8,7 @@ extension BaseChannelOperator on BaseChannel { /// See [https://docs.sendbird.com/platform/user_type#3_operator](https://docs.sendbird.com/platform/user_type#3_operator) for the explanations on the operators. Future addOperators(List userIds) async { sbLog.i(StackTrace.current, 'userIds: $userIds'); + checkUnsupportedAction(); if (userIds.isEmpty) { throw InvalidParameterException(); @@ -25,6 +26,7 @@ extension BaseChannelOperator on BaseChannel { /// See [https://docs.sendbird.com/platform/user_type#3_operator](https://docs.sendbird.com/platform/user_type#3_operator) for the explanations on the operators. Future removeOperators(List userIds) async { sbLog.i(StackTrace.current, 'userIds: $userIds'); + checkUnsupportedAction(); if (userIds.isEmpty) { throw InvalidParameterException(); @@ -42,6 +44,7 @@ extension BaseChannelOperator on BaseChannel { /// See [https://docs.sendbird.com/platform/user_type#3_operator](https://docs.sendbird.com/platform/user_type#3_operator) for the explanations on the operators. Future removeAllOperators() async { sbLog.i(StackTrace.current); + checkUnsupportedAction(); await chat.apiClient.send(ChannelOperatorsRemoveRequest( chat, diff --git a/lib/src/public/core/channel/base_channel/base_channel_reaction.dart b/lib/src/public/core/channel/base_channel/base_channel_reaction.dart index 6dc6a879..7b1e585f 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_reaction.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_reaction.dart @@ -7,6 +7,7 @@ extension BaseChannelReactions on BaseChannel { /// Adds `Reaction`. Future addReaction(BaseMessage message, String key) async { sbLog.i(StackTrace.current, 'messageId: ${message.messageId}, key: $key'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); @@ -33,6 +34,7 @@ extension BaseChannelReactions on BaseChannel { /// Deletes `Reaction`. Future deleteReaction(BaseMessage message, String key) async { sbLog.i(StackTrace.current, 'messageId: ${message.messageId}, key: $key'); + checkUnsupportedAction(); if (message.channelUrl != channelUrl) { throw InvalidParameterException(); diff --git a/lib/src/public/core/channel/feed_channel/feed_channel.dart b/lib/src/public/core/channel/feed_channel/feed_channel.dart new file mode 100644 index 00000000..1e8a7aee --- /dev/null +++ b/lib/src/public/core/channel/feed_channel/feed_channel.dart @@ -0,0 +1,158 @@ +// 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/main/extensions/extensions.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.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/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'; + +/// Represents a feed channel. +/// @since 4.0.3 +class FeedChannel extends BaseChannel { + /// The unique channel URL. + /// @since 4.0.3 + @override + String get channelUrl => groupChannel.channelUrl; + @override + set channelUrl(value) => groupChannel.channelUrl = value; + + /// The topic or name of the channel. + /// @since 4.0.3 + @override + String get name => groupChannel.name; + @override + set name(value) => groupChannel.name = value; + + /// The creation time of the channel. + /// @since 4.0.3 + @override + int? get createdAt => groupChannel.createdAt; + @override + set createdAt(value) => groupChannel.createdAt = value; + + /// Member list for this channel. + /// @since 4.0.3 + List get members => groupChannel.members; + + /// The total member count for this channel. + /// @since 4.0.3 + int get memberCount => groupChannel.memberCount; + + /// My member state. + /// @since 4.0.3 + MemberState get myMemberState => groupChannel.myMemberState; + + /// Current user's last read receipt timestamp in channel. + /// @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; + + GroupChannel groupChannel; + int _lastMarkAsReadTimestamp; + + FeedChannel({ + required this.groupChannel, + }) : _lastMarkAsReadTimestamp = 0, + super( + channelUrl: groupChannel.channelUrl, + name: groupChannel.name, + coverUrl: groupChannel.coverUrl, + createdAt: groupChannel.createdAt, + data: groupChannel.data, + customType: groupChannel.customType, + isFrozen: groupChannel.isFrozen, + isEphemeral: groupChannel.isEphemeral, + fromCache: false, + dirty: false, + ); + + static Future getChannel( + String channelUrl, { + Chat? chat, + }) async { + sbLog.i(StackTrace.current, 'channelUrl: $channelUrl'); + chat ??= SendbirdChat().chat; + + final channel = chat.channelCache.find(channelKey: channelUrl); + if (channel != null && !channel.dirty) { + channel.fromCache = true; + return channel; + } + return await FeedChannel.refresh(channelUrl, chat: chat); + } + + /// Refreshes all the data of this channel. + /// @since 4.0.3 + static Future refresh( + String channelUrl, { + Chat? chat, + }) async { + sbLog.i(StackTrace.current, 'channelUrl: $channelUrl'); + chat ??= SendbirdChat().chat; + + return await chat.apiClient.send( + FeedChannelRefreshRequest( + chat, + channelUrl, + options: [ + ChannelListQueryIncludeOption.includeMember, + ChannelListQueryIncludeOption.includeMetadata, + ChannelListQueryIncludeOption.includeReadReceipt, + ChannelListQueryIncludeOption.includeDeliveryReceipt, + ], + passive: false, + ), + ); + } + + /// Sends mark as read to this channel. + /// @since 4.0.3 + 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); + } + + @override + bool operator ==(other) { + if (identical(other, this)) return true; + if (!(super == (other))) return false; + + return other is FeedChannel && other.groupChannel == groupChannel; + } + + @override + int get hashCode => Object.hash( + super.hashCode, + groupChannel.hashCode, + ); + + @override + void copyWith(dynamic other) { + super.copyWith(other); + if (other is FeedChannel) { + groupChannel = other.groupChannel; + } + } +} 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 f92fbe87..7b6e036c 100644 --- a/lib/src/public/core/channel/group_channel/group_channel.dart +++ b/lib/src/public/core/channel/group_channel/group_channel.dart @@ -4,6 +4,7 @@ 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/delivery_status.dart'; import 'package:sendbird_chat_sdk/src/internal/main/model/read_status.dart'; @@ -131,6 +132,10 @@ class GroupChannel extends BaseChannel { @JsonKey(name: 'push_trigger_option') GroupChannelPushTriggerOption myPushTriggerOption; + /// Checks whether this channel is a chat notification [GroupChannel]. + /// @since 4.0.3 + bool isChatNotification; + /// My member state. @JsonKey(name: 'member_state') MemberState myMemberState; @@ -210,6 +215,7 @@ class GroupChannel extends BaseChannel { this.memberCount = 0, this.joinedMemberCount = 0, this.myPushTriggerOption = GroupChannelPushTriggerOption.defaultValue, + this.isChatNotification = false, this.myMemberState = MemberState.none, this.myRole = Role.none, this.myMutedState = MuteState.unmuted, @@ -392,6 +398,7 @@ class GroupChannel extends BaseChannel { other.memberCount == memberCount && other.joinedMemberCount == joinedMemberCount && other.myPushTriggerOption == myPushTriggerOption && + other.isChatNotification == isChatNotification && other.myMemberState == myMemberState && other.myRole == myRole && other.myMutedState == myMutedState && @@ -426,36 +433,37 @@ class GroupChannel extends BaseChannel { ); @override - void copyWith(dynamic others) { - super.copyWith(others); - if (others is GroupChannel) { - lastMessage = others.lastMessage; - isSuper = others.isSuper; - isBroadcast = others.isBroadcast; - isPublic = others.isPublic; - isDistinct = others.isDistinct; - isDiscoverable = others.isDiscoverable; - isExclusive = others.isExclusive; - isAccessCodeRequired = others.isAccessCodeRequired; - unreadMessageCount = others.unreadMessageCount; - unreadMentionCount = others.unreadMentionCount; - members = List.from(others.members); - memberCount = others.memberCount; - joinedMemberCount = others.joinedMemberCount; - myPushTriggerOption = others.myPushTriggerOption; - myMemberState = others.myMemberState; - myRole = others.myRole; - myMutedState = others.myMutedState; - myCountPreference = others.myCountPreference; - creator = others.creator; - inviter = others.inviter; - invitedAt = others.invitedAt; - joinedAt = others.joinedAt; - isHidden = others.isHidden; - hiddenState = others.hiddenState; - myLastRead = others.myLastRead; - messageOffsetTimestamp = others.messageOffsetTimestamp; - messageSurvivalSeconds = others.messageSurvivalSeconds; + void copyWith(dynamic other) { + super.copyWith(other); + if (other is GroupChannel) { + lastMessage = other.lastMessage; + isSuper = other.isSuper; + isBroadcast = other.isBroadcast; + isPublic = other.isPublic; + isDistinct = other.isDistinct; + isDiscoverable = other.isDiscoverable; + isExclusive = other.isExclusive; + isAccessCodeRequired = other.isAccessCodeRequired; + unreadMessageCount = other.unreadMessageCount; + unreadMentionCount = other.unreadMentionCount; + members = List.from(other.members); + memberCount = other.memberCount; + joinedMemberCount = other.joinedMemberCount; + myPushTriggerOption = other.myPushTriggerOption; + isChatNotification = other.isChatNotification; + myMemberState = other.myMemberState; + myRole = other.myRole; + myMutedState = other.myMutedState; + myCountPreference = other.myCountPreference; + creator = other.creator; + inviter = other.inviter; + invitedAt = other.invitedAt; + joinedAt = other.joinedAt; + isHidden = other.isHidden; + hiddenState = other.hiddenState; + myLastRead = other.myLastRead; + messageOffsetTimestamp = other.messageOffsetTimestamp; + messageSurvivalSeconds = other.messageSurvivalSeconds; } } } 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 4eb889be..34906bc0 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 @@ -30,6 +30,7 @@ GroupChannel _$GroupChannelFromJson(Map json) => GroupChannel( _$GroupChannelPushTriggerOptionEnumMap, json['push_trigger_option']) ?? GroupChannelPushTriggerOption.defaultValue, + isChatNotification: json['is_chat_notification'] as bool? ?? false, myMemberState: $enumDecodeNullable(_$MemberStateEnumMap, json['member_state']) ?? MemberState.none, 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 c96c1682..ed626c1d 100644 --- a/lib/src/public/core/channel/open_channel/open_channel.dart +++ b/lib/src/public/core/channel/open_channel/open_channel.dart @@ -4,6 +4,7 @@ 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/network/http/http_client/request/channel/open_channel/open_channel_create_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/open_channel/open_channel_delete_request.dart'; @@ -13,7 +14,6 @@ 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'; @@ -36,7 +36,6 @@ class OpenChannel extends BaseChannel { required String channelUrl, String name = '', String coverUrl = '', - User? creator, int? createdAt, String data = '', String customType = '', @@ -162,11 +161,11 @@ class OpenChannel extends BaseChannel { ); @override - void copyWith(dynamic others) { - super.copyWith(others); - if (others is OpenChannel) { - participantCount = others.participantCount; - operators = List.from(others.operators); + void copyWith(dynamic other) { + super.copyWith(other); + if (other is OpenChannel) { + participantCount = other.participantCount; + operators = List.from(other.operators); } } } diff --git a/lib/src/public/core/message/admin_message.g.dart b/lib/src/public/core/message/admin_message.g.dart index 2f12fa8c..d895cb50 100644 --- a/lib/src/public/core/message/admin_message.g.dart +++ b/lib/src/public/core/message/admin_message.g.dart @@ -58,6 +58,7 @@ AdminMessage _$AdminMessageFromJson(Map json) => AdminMessage( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$SendingStatusEnumMap = { diff --git a/lib/src/public/core/message/file_message.dart b/lib/src/public/core/message/file_message.dart index ca93d4be..25654ff6 100644 --- a/lib/src/public/core/message/file_message.dart +++ b/lib/src/public/core/message/file_message.dart @@ -52,6 +52,10 @@ class FileMessage extends BaseMessage { /// please refer to [BaseChannelMessage.sendFileMessage]. final List? thumbnails; + /// [FileMessageCreateParams] object that used for sending this message. + @JsonKey(includeFromJson: false, includeToJson: false) + FileMessageCreateParams? messageCreateParams; + final bool requireAuth; @JsonKey(includeFromJson: false, includeToJson: false) diff --git a/lib/src/public/core/message/file_message.g.dart b/lib/src/public/core/message/file_message.g.dart index 4d2751c0..1be1da28 100644 --- a/lib/src/public/core/message/file_message.g.dart +++ b/lib/src/public/core/message/file_message.g.dart @@ -75,6 +75,7 @@ const _$SendingStatusEnumMap = { const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$MentionTypeEnumMap = { diff --git a/lib/src/public/core/message/user_message.dart b/lib/src/public/core/message/user_message.dart index a0bf7321..4f5c30df 100644 --- a/lib/src/public/core/message/user_message.dart +++ b/lib/src/public/core/message/user_message.dart @@ -16,6 +16,7 @@ import 'package:sendbird_chat_sdk/src/public/main/model/og/og_meta_data.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/poll/poll.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/reaction/reaction.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/thread/thread_info.dart'; +import 'package:sendbird_chat_sdk/src/public/main/params/message/user_message_create_params.dart'; part 'user_message.g.dart'; @@ -36,6 +37,10 @@ class UserMessage extends BaseMessage { /// The poll that belongs to this message object. Poll? poll; + /// [UserMessageCreateParams] object that used for sending this message. + @JsonKey(includeFromJson: false, includeToJson: false) + UserMessageCreateParams? messageCreateParams; + UserMessage({ required this.translations, required int messageId, diff --git a/lib/src/public/core/message/user_message.g.dart b/lib/src/public/core/message/user_message.g.dart index f20b5a4f..c129bc58 100644 --- a/lib/src/public/core/message/user_message.g.dart +++ b/lib/src/public/core/message/user_message.g.dart @@ -70,6 +70,7 @@ UserMessage _$UserMessageFromJson(Map json) => UserMessage( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$SendingStatusEnumMap = { diff --git a/lib/src/public/main/chat/sendbird_chat.dart b/lib/src/public/main/chat/sendbird_chat.dart index eeca12e9..8a7bdf5a 100644 --- a/lib/src/public/main/chat/sendbird_chat.dart +++ b/lib/src/public/main/chat/sendbird_chat.dart @@ -13,17 +13,24 @@ import 'package:sendbird_chat_sdk/src/public/main/handler/channel_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/connection_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/session_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/handler/user_event_handler.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/channel/feed_channel_change_logs.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/channel/group_channel_change_logs.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/channel/group_channel_unread_item_count.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/chat/do_not_disturb.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/chat/emoji.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/global_notification_channel_setting.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/notification_template.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/chat/notification_template_list.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/chat/snooze_period.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/info/app_info.dart'; import 'package:sendbird_chat_sdk/src/public/main/model/info/file_info.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/message/unread_message_count.dart'; +import 'package:sendbird_chat_sdk/src/public/main/params/channel/feed_channel_change_logs_params.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/group_channel_change_logs_params.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/group_channel_total_unread_channel_count_params.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/channel/group_channel_total_unread_message_count_params.dart'; import 'package:sendbird_chat_sdk/src/public/main/params/message/total_scheduled_message_count_params.dart'; +import 'package:sendbird_chat_sdk/src/public/main/params/notifications/notification_template_list_params.dart'; /// An object represents a main class to use Sendbird Chat class SendbirdChat { @@ -153,6 +160,18 @@ class SendbirdChat { token: token, timestamp: timestamp); } + /// Requests the channel changelogs from given token. + /// @since 4.0.3 + static Future getMyFeedChannelChangeLogs( + FeedChannelChangeLogsParams params, { + String? token, + int? timestamp, + }) async { + sbLog.i(StackTrace.current, 'token: $token'); + return _instance._chat + .getMyFeedChannelChangeLogs(params, token: token, timestamp: timestamp); + } + /// Sends mark as read to all joined `GroupChannel`s. /// This method has rate limit. You can send one request per second. static Future markAsReadAll() async { @@ -194,6 +213,16 @@ class SendbirdChat { [GroupChannelTotalUnreadMessageCountParams? params]) async { final result = await _instance._chat.getTotalUnreadMessageCount(params); sbLog.i(StackTrace.current, 'return: $result'); + return result.totalCountForGroupChannels; + } + + /// Gets the total number of unread message of `GroupChannel`s and `FeedChannel`s + /// with [GroupChannelTotalUnreadMessageCountParams] filter. + /// @since 4.0.3 + static Future getTotalUnreadMessageCountWithFeedChannel( + [GroupChannelTotalUnreadMessageCountParams? params]) async { + final result = await _instance._chat.getTotalUnreadMessageCount(params); + sbLog.i(StackTrace.current, 'return: $result'); return result; } @@ -602,4 +631,37 @@ class SendbirdChat { sbLog.i(StackTrace.current); return await _instance._chat.getSnoozePeriod(); } + +//------------------------------// +// Notifications +//------------------------------// + /// Retrieves Global Notification channel theme. + /// @since 4.0.3 + static Future + getGlobalNotificationChannelSetting() async { + sbLog.i(StackTrace.current); + return await _instance._chat.getGlobalNotificationChannelSetting(); + } + + /// Retrieves Notification template list by token. + /// [token] is the value to retrieve notification template list from. + /// @since 4.0.3 + static Future getNotificationTemplateListByToken( + NotificationTemplateListParams params, { + String? token, + }) async { + sbLog.i(StackTrace.current); + return await _instance._chat + .getNotificationTemplateListByToken(params, token: token); + } + + /// Retrieves Notification template. + /// [key] is the template key. + /// @since 4.0.3 + static Future getNotificationTemplate({ + required String key, + }) async { + sbLog.i(StackTrace.current); + return await _instance._chat.getNotificationTemplate(key: key); + } } diff --git a/lib/src/public/main/collection/group_channel_collection/base_channel_context.dart b/lib/src/public/main/collection/group_channel_collection/base_channel_context.dart new file mode 100644 index 00000000..739436dc --- /dev/null +++ b/lib/src/public/main/collection/group_channel_collection/base_channel_context.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; + +/// The context of a base channel. +/// @since 4.0.3 +class BaseChannelContext { + final CollectionEventSource _collectionEventSource; + + /// The [CollectionEventSource] of current context. + CollectionEventSource get collectionEventSource => _collectionEventSource; + + BaseChannelContext(CollectionEventSource collectionEventSource) + : _collectionEventSource = collectionEventSource; +} diff --git a/lib/src/public/main/collection/group_channel_collection/feed_channel_context.dart b/lib/src/public/main/collection/group_channel_collection/feed_channel_context.dart new file mode 100644 index 00000000..64259399 --- /dev/null +++ b/lib/src/public/main/collection/group_channel_collection/feed_channel_context.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_collection/base_channel_context.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart'; + +/// The context of a feed channel, used in [NotificationCollectionHandler]. +/// @since 4.0.3 +class FeedChannelContext extends BaseChannelContext { + /// Constructor + /// @since 4.0.3 + FeedChannelContext(CollectionEventSource collectionEventSource) + : super(collectionEventSource); +} diff --git a/lib/src/public/main/collection/group_channel_collection/group_channel_context.dart b/lib/src/public/main/collection/group_channel_collection/group_channel_context.dart index 0a576b95..169701fb 100644 --- a/lib/src/public/main/collection/group_channel_collection/group_channel_context.dart +++ b/lib/src/public/main/collection/group_channel_collection/group_channel_context.dart @@ -1,15 +1,11 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_collection/base_channel_context.dart'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_collection/group_channel_collection_handler.dart'; -/// The context of a channel, used in [GroupChannelCollectionHandler]. -class GroupChannelContext { - final CollectionEventSource _collectionEventSource; - - /// The [CollectionEventSource] of current context. - CollectionEventSource get collectionEventSource => _collectionEventSource; - +/// The context of a group channel, used in [GroupChannelCollectionHandler]. +class GroupChannelContext extends BaseChannelContext { GroupChannelContext(CollectionEventSource collectionEventSource) - : _collectionEventSource = collectionEventSource; + : super(collectionEventSource); } 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 new file mode 100644 index 00000000..f65427db --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/base_message_collection.dart @@ -0,0 +1,305 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/sendbird_chat_sdk.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/chat_manager/collection_manager/collection_manager.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/message/channel_messages_get_request.dart'; + +/// The base message collection that handles message lists. +/// @since 4.0.3 +class BaseMessageCollection { + /// The list of succeeded message list in this collection. + final List messageList = []; + + /// The starting point of the collection. + int get startingPoint => _startingPoint; + + /// Whether [initialize] method is called. + bool get isInitialized => _isInitialized; + + /// Whether there's more data to load in previous (oldest) direction. + bool get hasPrevious => _hasPrevious; + + /// Whether there's more data to load in next (latest) direction. + bool get hasNext => _hasNext; + + /// Whether this collection is loading. + bool get isLoading => _isLoading; + + /// Whether this collection is disposed. + bool get isDisposed => _isDisposed; + + /// The [MessageListParams]. + MessageListParams get params => _initializeParams; + + BaseChannel get baseChannel => _channel; + BaseMessageCollectionHandler get baseHandler => _handler; + + set hasNext(value) => _hasNext = value; + MessageListParams get loadPreviousParams => _loadPreviousParams; + MessageListParams get loadNextParams => _loadNextParams; + Chat get chat => _chat; + BaseMessage? get latestMessage => _latestMessage; + + final BaseChannel _channel; + final BaseMessageCollectionHandler _handler; + + final MessageListParams _initializeParams; + final MessageListParams _loadPreviousParams; + final MessageListParams _loadNextParams; + final int _startingPoint; + final Chat _chat; + bool _isInitialized = false; + bool _isDisposed = false; + bool _isLoading = false; + bool _hasPrevious = false; + bool _hasNext = false; + BaseMessage? _oldestMessage; + BaseMessage? _latestMessage; + + BaseMessageCollection({ + required BaseChannel channel, + required MessageListParams params, + required BaseMessageCollectionHandler handler, + required int startingPoint, + required Chat chat, + }) : _channel = channel, + _initializeParams = params, + _loadPreviousParams = MessageListParams()..copyWith(params), + _loadNextParams = MessageListParams()..copyWith(params), + _handler = handler, + _startingPoint = startingPoint, + _chat = chat { + sbLog.i(StackTrace.current, 'BaseMessageCollection()'); + _chat.collectionManager.addMessageCollection(this); + } + + /// Disposes current [MessageCollection] and stops all events from being received. + void dispose() { + sbLog.i(StackTrace.current, 'dispose()'); + messageList.clear(); + _chat.collectionManager.removeMessageCollection(this); + _isDisposed = true; + } + + /// Initializes this collection from [startingPoint]. + Future initialize() async { + if (_isDisposed) return; + if (_isInitialized) { + throw InvalidInitializationException(); + } + + sbLog.i(StackTrace.current, 'initialize()'); + _isInitialized = true; + + _initializeParams.inclusive = true; + + final messages = + await _chat.apiClient.send>(ChannelMessagesGetRequest( + _chat, + channelType: ChannelType.group, + channelUrl: _channel.channelUrl, + params: _initializeParams.toJson(), + timestamp: _startingPoint, + )); + + if (_isDisposed) return; + + if (messages.isNotEmpty) { + final oldestMessage = + _initializeParams.reverse ? messages.last : messages.first; + final latestMessage = + _initializeParams.reverse ? messages.first : messages.last; + _oldestMessage = oldestMessage; + _latestMessage = latestMessage; + + final maxCreatedAt = messages + .reduce((current, next) => + current.createdAt > next.createdAt ? current : next) + .createdAt; + + if (_startingPoint > maxCreatedAt) { + _hasPrevious = messages.length == _initializeParams.previousResultSize; + _hasNext = false; + } else if (_startingPoint == 0) { + _hasPrevious = false; + _hasNext = messages.length == _initializeParams.nextResultSize; + } else { + final previousMessages = + messages.where((e) => e.createdAt < _startingPoint).toList(); + final nextMessages = + messages.where((e) => e.createdAt > _startingPoint).toList(); + + _hasPrevious = previousMessages.length - + _getExistedMessageCountInMessageList(previousMessages) == + _initializeParams.previousResultSize; + _hasNext = nextMessages.length - + _getExistedMessageCountInMessageList(nextMessages) == + _initializeParams.nextResultSize; + } + } else { + _hasPrevious = false; + _hasNext = false; + } + + if (messages.isNotEmpty) { + _chat.collectionManager.sendEventsToMessageCollection( + messageCollection: this, + baseChannel: _channel, + eventSource: CollectionEventSource.messageInitialize, + sendingStatus: SendingStatus.succeeded, + addedMessages: messages, + isReversedAddedMessages: _initializeParams.reverse, + ); + } + } + + /// Loads previous (oldest direction) message lists. + Future loadPrevious() async { + if (_isDisposed) { + throw InvalidCollectionDisposedException(); + } + if (!_isInitialized) return; + + if (_isLoading) throw QueryInProgressException(); + if (!_hasPrevious) return; + if (_oldestMessage == null) return; + + sbLog.i(StackTrace.current, 'loadPrevious()'); + _isLoading = true; + + _loadPreviousParams + ..nextResultSize = 0 + ..inclusive = true; + + List messages = + await _chat.apiClient.send>(ChannelMessagesGetRequest( + _chat, + channelType: ChannelType.group, + channelUrl: _channel.channelUrl, + params: _loadPreviousParams.toJson(), + timestamp: _oldestMessage!.createdAt, + )); + + if (_isDisposed) return; + + if (messages.isNotEmpty) { + final oldestMessage = + _loadPreviousParams.reverse ? messages.last : messages.first; + _oldestMessage = oldestMessage; + + _hasPrevious = + messages.length - _getExistedMessageCountInMessageList(messages) == + _loadPreviousParams.previousResultSize; + } else { + _hasPrevious = false; + } + + _isLoading = false; + + if (messages.isNotEmpty) { + _chat.collectionManager.sendEventsToMessageCollection( + messageCollection: this, + baseChannel: baseChannel, + eventSource: CollectionEventSource.messageLoadPrevious, + sendingStatus: SendingStatus.succeeded, + addedMessages: messages, + isReversedAddedMessages: + !_loadPreviousParams.reverse, // Added ! because of loadPrevious() + ); + } + } + + /// Loads next (latest direction) message lists. + Future loadNext() async { + if (_isDisposed) { + throw InvalidCollectionDisposedException(); + } + if (!_isInitialized) return; + + if (_isLoading) throw QueryInProgressException(); + if (!_hasNext) return; + if (_latestMessage == null) return; + + sbLog.i(StackTrace.current, 'loadNext()'); + _isLoading = true; + + _loadNextParams + ..previousResultSize = 0 + ..inclusive = true; + + List messages = + await _chat.apiClient.send>(ChannelMessagesGetRequest( + _chat, + channelType: ChannelType.group, + channelUrl: _channel.channelUrl, + params: _loadNextParams.toJson(), + timestamp: _latestMessage!.createdAt, + )); + + if (_isDisposed) return; + + if (messages.isNotEmpty) { + final latestMessage = + _loadNextParams.reverse ? messages.first : messages.last; + _latestMessage = latestMessage; + + _hasNext = + messages.length - _getExistedMessageCountInMessageList(messages) == + _loadNextParams.nextResultSize; + } else { + _hasNext = false; + } + + _isLoading = false; + + if (messages.isNotEmpty) { + _chat.collectionManager.sendEventsToMessageCollection( + messageCollection: this, + baseChannel: baseChannel, + eventSource: CollectionEventSource.messageLoadNext, + sendingStatus: SendingStatus.succeeded, + addedMessages: messages, + isReversedAddedMessages: _loadNextParams.reverse, + ); + } + } + + bool canAddMessage( + CollectionEventSource eventSource, + BaseMessage addedMessage, + ) { + if (eventSource == CollectionEventSource.messageLoadPrevious || + eventSource == CollectionEventSource.messageLoadNext) { + return true; + } + + if (messageList.isEmpty) { + return false; + } + + if (messageList.isNotEmpty && hasNext) { + final a = messageList.last; + final b = addedMessage; + + if (a.createdAt < b.createdAt) { + return false; + } + } + return true; + } + + int _getExistedMessageCountInMessageList(List loadedMessages) { + int existedMessageCount = 0; + for (final loadedMessage in loadedMessages) { + for (final message in messageList) { + if (loadedMessage.messageId == message.messageId) { + existedMessageCount++; + break; + } + } + } + return existedMessageCount; + } +} diff --git a/lib/src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart b/lib/src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart new file mode 100644 index 00000000..72704986 --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/base_message_collection_handler.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +/// The base message collection handler. +/// @since 4.0.3 +abstract class BaseMessageCollectionHandler {} diff --git a/lib/src/public/main/collection/group_channel_message_collection/base_message_context.dart b/lib/src/public/main/collection/group_channel_message_collection/base_message_context.dart new file mode 100644 index 00000000..dfee921b --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/base_message_context.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; + +/// The context of a base message. +/// @since 4.0.3 +class BaseMessageContext { + final CollectionEventSource _collectionEventSource; + final SendingStatus _sendingStatus; // TODO: Remove (?) + + /// The [CollectionEventSource] of current context. + CollectionEventSource get collectionEventSource => _collectionEventSource; + + /// The [sendingStatus] of the messages that's sent out from [MessageCollectionHandler] with this context. + SendingStatus get sendingStatus => _sendingStatus; + + BaseMessageContext( + CollectionEventSource collectionEventSource, SendingStatus sendingStatus) + : _collectionEventSource = collectionEventSource, + _sendingStatus = sendingStatus; +} 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 7c188055..0872ff19 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 @@ -2,64 +2,19 @@ import 'package:sendbird_chat_sdk/sendbird_chat_sdk.dart'; import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; -import 'package:sendbird_chat_sdk/src/internal/main/chat_manager/collection_manager/collection_manager.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/network/http/http_client/request/channel/message/channel_messages_get_request.dart'; - -/// Collection that handles message lists. -class MessageCollection { - /// The list of succeeded message list in this collection. - final List messageList = []; +/// Message collection that handles message lists. +class MessageCollection extends BaseMessageCollection { /// The [GroupChannel] tracked by this [MessageCollection]. - GroupChannel get channel => _channel; + GroupChannel get channel => baseChannel as GroupChannel; /// The message collection handler to be used for this [MessageCollection]. - MessageCollectionHandler get handler => _handler; - - /// The starting point of the collection. - int get startingPoint => _startingPoint; - - /// Whether [initialize] method is called. - bool get isInitialized => _isInitialized; - - /// Whether there's more data to load in previous (oldest) direction. - bool get hasPrevious => _hasPrevious; - - /// Whether there's more data to load in next (latest) direction. - bool get hasNext => _hasNext; - - /// Whether this collection is loading. - bool get isLoading => _isLoading; - - /// Whether this collection is disposed. - bool get isDisposed => _isDisposed; + MessageCollectionHandler get handler => + baseHandler as MessageCollectionHandler; - /// The [MessageListParams]. - MessageListParams get params => _initializeParams; - - set hasNext(value) => _hasNext = value; - MessageListParams get loadPreviousParams => _loadPreviousParams; - MessageListParams get loadNextParams => _loadNextParams; - BaseMessage? get latestMessage => _latestMessage; - - final GroupChannel _channel; - final MessageListParams _initializeParams; - final MessageListParams _loadPreviousParams; - final MessageListParams _loadNextParams; - final MessageCollectionHandler _handler; - final int _startingPoint; - final Chat _chat; - bool _isInitialized = false; - bool _isDisposed = false; - bool _isLoading = false; - bool _hasPrevious = false; - bool _hasNext = false; - BaseMessage? _oldestMessage; - BaseMessage? _latestMessage; - - /// Constructor + /// Constructor for MessageCollection. /// /// [startingPoint] is the reference point for message retrieval in a chat view. /// This should be specified as a timestamp(ms) and the default value is max. @@ -69,242 +24,13 @@ class MessageCollection { required MessageCollectionHandler handler, int? startingPoint, Chat? chat, - }) : _channel = channel, - _initializeParams = params, - _loadPreviousParams = MessageListParams()..copyWith(params), - _loadNextParams = MessageListParams()..copyWith(params), - _handler = handler, - _startingPoint = startingPoint ?? IntMax.max, - _chat = chat ?? SendbirdChat().chat { + }) : super( + channel: channel, + params: params, + handler: handler, + startingPoint: startingPoint ?? IntMax.max, + chat: chat ?? SendbirdChat().chat, + ) { sbLog.i(StackTrace.current, 'MessageCollection()'); - _chat.collectionManager.addMessageCollection(this); - } - - /// Disposes current [MessageCollection] and stops all events from being received. - void dispose() { - sbLog.i(StackTrace.current, 'dispose()'); - messageList.clear(); - _chat.collectionManager.removeMessageCollection(this); - _isDisposed = true; - } - - /// Initializes this collection from [startingPoint]. - Future initialize() async { - if (_isDisposed) return; - if (_isInitialized) { - throw InvalidInitializationException(); - } - - sbLog.i(StackTrace.current, 'initialize()'); - _isInitialized = true; - - _initializeParams.inclusive = true; - - final messages = - await _chat.apiClient.send>(ChannelMessagesGetRequest( - _chat, - channelType: ChannelType.group, - channelUrl: _channel.channelUrl, - params: _initializeParams.toJson(), - timestamp: _startingPoint, - )); - - if (_isDisposed) return; - - if (messages.isNotEmpty) { - final oldestMessage = - _initializeParams.reverse ? messages.last : messages.first; - final latestMessage = - _initializeParams.reverse ? messages.first : messages.last; - _oldestMessage = oldestMessage; - _latestMessage = latestMessage; - - final maxCreatedAt = messages - .reduce((current, next) => - current.createdAt > next.createdAt ? current : next) - .createdAt; - - if (_startingPoint > maxCreatedAt) { - _hasPrevious = messages.length == _initializeParams.previousResultSize; - _hasNext = false; - } else if (_startingPoint == 0) { - _hasPrevious = false; - _hasNext = messages.length == _initializeParams.nextResultSize; - } else { - final previousMessages = - messages.where((e) => e.createdAt < _startingPoint).toList(); - final nextMessages = - messages.where((e) => e.createdAt > _startingPoint).toList(); - - _hasPrevious = previousMessages.length - - _getExistedMessageCountInMessageList(previousMessages) == - _initializeParams.previousResultSize; - _hasNext = nextMessages.length - - _getExistedMessageCountInMessageList(nextMessages) == - _initializeParams.nextResultSize; - } - } else { - _hasPrevious = false; - _hasNext = false; - } - - if (messages.isNotEmpty) { - _chat.collectionManager.sendEventsToMessageCollection( - messageCollection: this, - groupChannel: _channel, - eventSource: CollectionEventSource.messageInitialize, - sendingStatus: SendingStatus.succeeded, - addedMessages: messages, - isReversedAddedMessages: _initializeParams.reverse, - ); - } - } - - /// Loads previous (oldest direction) message lists. - Future loadPrevious() async { - if (_isDisposed) { - throw InvalidCollectionDisposedException(); - } - if (!_isInitialized) return; - - if (_isLoading) throw QueryInProgressException(); - if (!_hasPrevious) return; - if (_oldestMessage == null) return; - - sbLog.i(StackTrace.current, 'loadPrevious()'); - _isLoading = true; - - _loadPreviousParams - ..nextResultSize = 0 - ..inclusive = true; - - List messages = - await _chat.apiClient.send>(ChannelMessagesGetRequest( - _chat, - channelType: ChannelType.group, - channelUrl: _channel.channelUrl, - params: _loadPreviousParams.toJson(), - timestamp: _oldestMessage!.createdAt, - )); - - if (_isDisposed) return; - - if (messages.isNotEmpty) { - final oldestMessage = - _loadPreviousParams.reverse ? messages.last : messages.first; - _oldestMessage = oldestMessage; - - _hasPrevious = - messages.length - _getExistedMessageCountInMessageList(messages) == - _loadPreviousParams.previousResultSize; - } else { - _hasPrevious = false; - } - - _isLoading = false; - - if (messages.isNotEmpty) { - _chat.collectionManager.sendEventsToMessageCollection( - messageCollection: this, - groupChannel: channel, - eventSource: CollectionEventSource.messageLoadPrevious, - sendingStatus: SendingStatus.succeeded, - addedMessages: messages, - isReversedAddedMessages: - !_loadPreviousParams.reverse, // Added ! because of loadPrevious() - ); - } - } - - /// Loads next (latest direction) message lists. - Future loadNext() async { - if (_isDisposed) { - throw InvalidCollectionDisposedException(); - } - if (!_isInitialized) return; - - if (_isLoading) throw QueryInProgressException(); - if (!_hasNext) return; - if (_latestMessage == null) return; - - sbLog.i(StackTrace.current, 'loadNext()'); - _isLoading = true; - - _loadNextParams - ..previousResultSize = 0 - ..inclusive = true; - - List messages = - await _chat.apiClient.send>(ChannelMessagesGetRequest( - _chat, - channelType: ChannelType.group, - channelUrl: _channel.channelUrl, - params: _loadNextParams.toJson(), - timestamp: _latestMessage!.createdAt, - )); - - if (_isDisposed) return; - - if (messages.isNotEmpty) { - final latestMessage = - _loadNextParams.reverse ? messages.first : messages.last; - _latestMessage = latestMessage; - - _hasNext = - messages.length - _getExistedMessageCountInMessageList(messages) == - _loadNextParams.nextResultSize; - } else { - _hasNext = false; - } - - _isLoading = false; - - if (messages.isNotEmpty) { - _chat.collectionManager.sendEventsToMessageCollection( - messageCollection: this, - groupChannel: channel, - eventSource: CollectionEventSource.messageLoadNext, - sendingStatus: SendingStatus.succeeded, - addedMessages: messages, - isReversedAddedMessages: _loadNextParams.reverse, - ); - } - } - - bool canAddMessage( - CollectionEventSource eventSource, - BaseMessage addedMessage, - ) { - if (eventSource == CollectionEventSource.messageLoadPrevious || - eventSource == CollectionEventSource.messageLoadNext) { - return true; - } - - if (messageList.isEmpty) { - return false; - } - - if (messageList.isNotEmpty && hasNext) { - final a = messageList.last; - final b = addedMessage; - - if (a.createdAt < b.createdAt) { - return false; - } - } - return true; - } - - int _getExistedMessageCountInMessageList(List loadedMessages) { - int existedMessageCount = 0; - for (final loadedMessage in loadedMessages) { - for (final message in messageList) { - if (loadedMessage.messageId == message.messageId) { - existedMessageCount++; - break; - } - } - } - return existedMessageCount; } } diff --git a/lib/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart b/lib/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart index 16739d31..e9f0ee0d 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart @@ -3,12 +3,13 @@ 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/main/collection/group_channel_collection/group_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'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/message_context.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; -/// An interface used in [MessageCollection]. -abstract class MessageCollectionHandler { +/// A handler used in [MessageCollection]. +abstract class MessageCollectionHandler extends BaseMessageCollectionHandler { /// Called when one or more [BaseMessage] is added to this collection. /// /// [context] is a context of where this addition happened. diff --git a/lib/src/public/main/collection/group_channel_message_collection/message_context.dart b/lib/src/public/main/collection/group_channel_message_collection/message_context.dart index 467a1c97..2b40d5e9 100644 --- a/lib/src/public/main/collection/group_channel_message_collection/message_context.dart +++ b/lib/src/public/main/collection/group_channel_message_collection/message_context.dart @@ -1,22 +1,13 @@ // Copyright (c) 2023 Sendbird, Inc. All rights reserved. import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/base_message_context.dart'; import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/message_collection_handler.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; /// The context of a message, used in [MessageCollectionHandler]. -class MessageContext { - final CollectionEventSource _collectionEventSource; - final SendingStatus _sendingStatus; // TODO: Remove (?) - - /// The [CollectionEventSource] of current context. - CollectionEventSource get collectionEventSource => _collectionEventSource; - - /// The [sendingStatus] of the messages that's sent out from [MessageCollectionHandler] with this context. - SendingStatus get sendingStatus => _sendingStatus; - +class MessageContext extends BaseMessageContext { MessageContext( CollectionEventSource collectionEventSource, SendingStatus sendingStatus) - : _collectionEventSource = collectionEventSource, - _sendingStatus = sendingStatus; + : super(collectionEventSource, sendingStatus); } 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 new file mode 100644 index 00000000..fa75ee45 --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/notification_collection.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/sendbird_chat_sdk.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'; + +/// Notification collection that handles message lists. +/// @since 4.0.3 +class NotificationCollection extends BaseMessageCollection { + /// The [FeedChannel] tracked by this [NotificationCollection]. + /// @since 4.0.3 + FeedChannel get channel => baseChannel as FeedChannel; + + /// The notification collection handler to be used for this [NotificationCollection]. + /// @since 4.0.3 + NotificationCollectionHandler get handler => + baseHandler as NotificationCollectionHandler; + + /// Constructor for NotificationCollection. + /// + /// [startingPoint] is the reference point for message retrieval in a chat view. + /// This should be specified as a timestamp(ms) and the default value is max. + /// @since 4.0.3 + NotificationCollection({ + required FeedChannel channel, + required MessageListParams params, + required NotificationCollectionHandler handler, + int? startingPoint, + Chat? chat, + }) : super( + channel: channel, + params: params, + handler: handler, + startingPoint: startingPoint ?? IntMax.max, + chat: chat ?? SendbirdChat().chat, + ) { + sbLog.i(StackTrace.current, 'MessageCollection()'); + } +} 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 new file mode 100644 index 00000000..570a6117 --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart @@ -0,0 +1,76 @@ +// 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/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'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/notification_collection.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/notification_context.dart'; +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. + /// + /// [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 + void onMessagesAdded( + NotificationContext context, + FeedChannel channel, + List messages, + ); + + /// Called when one or more [BaseMessage] 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 + void onMessagesUpdated( + NotificationContext context, + FeedChannel channel, + List messages, + ); + + /// Called when one or more [BaseMessage] 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 + void onMessagesDeleted( + NotificationContext context, + FeedChannel channel, + 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, + ); + + /// Called when the channel this collection holds is deleted. + /// + /// [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, + ); + + /// 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/collection/group_channel_message_collection/notification_context.dart b/lib/src/public/main/collection/group_channel_message_collection/notification_context.dart new file mode 100644 index 00000000..f780dde4 --- /dev/null +++ b/lib/src/public/main/collection/group_channel_message_collection/notification_context.dart @@ -0,0 +1,16 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/collection/collection_event_source.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/base_message_context.dart'; +import 'package:sendbird_chat_sdk/src/public/main/collection/group_channel_message_collection/notification_collection_handler.dart'; +import 'package:sendbird_chat_sdk/src/public/main/define/enums.dart'; + +/// The context of a notification, used in [NotificationCollectionHandler]. +/// @since 4.0.3 +class NotificationContext extends BaseMessageContext { + /// Constructor + /// @since 4.0.3 + NotificationContext( + CollectionEventSource collectionEventSource, SendingStatus sendingStatus) + : super(collectionEventSource, sendingStatus); +} diff --git a/lib/src/public/main/define/enums.dart b/lib/src/public/main/define/enums.dart index fe454ee1..119781af 100644 --- a/lib/src/public/main/define/enums.dart +++ b/lib/src/public/main/define/enums.dart @@ -32,6 +32,9 @@ enum SendingStatus { enum ChannelType { group, open, + + /// @since 4.0.3 + feed, } /// MentionType @@ -79,7 +82,7 @@ enum MembershipFilter { /// MessageTypeFilter enum MessageTypeFilter { - @JsonValue(null) + @JsonValue('') all, @JsonValue('MESG') user, @@ -250,16 +253,6 @@ enum UnreadItemKey { groupChannelInvitationCount, } -/// ChannelListQueryIncludeOption -enum ChannelListQueryIncludeOption { - includeEmpty, - includeMember, - includeFrozen, - includeMetadata, - includeReadReceipt, - includeDeliveryReceipt, -} - /// ReactionEventAction enum ReactionEventAction { @JsonValue('ADD') diff --git a/lib/src/public/main/handler/channel_handler.dart b/lib/src/public/main/handler/channel_handler.dart index d774df50..aeca3700 100644 --- a/lib/src/public/main/handler/channel_handler.dart +++ b/lib/src/public/main/handler/channel_handler.dart @@ -26,58 +26,73 @@ abstract class BaseChannelHandler { void onChannelDeleted(String channelUrl, ChannelType channelType) {} /// A callback for when a reactionEvent is updated. + /// Not for FeedChannel void onReactionUpdated(BaseChannel channel, ReactionEvent event) {} /// A callback for when a user is muted from channel. + /// Not for FeedChannel void onUserMuted(BaseChannel channel, RestrictedUser restrictedUser) {} /// A callback for when a user is unmuted from channel. + /// Not for FeedChannel void onUserUnmuted(BaseChannel channel, User user) {} /// A callback for when a user is banned from channel. + /// Not for FeedChannel void onUserBanned(BaseChannel channel, RestrictedUser restrictedUser) {} /// A callback for when a user is unbanned from channel. + /// Not for FeedChannel void onUserUnbanned(BaseChannel channel, User user) {} /// A callback for when channel is frozen (Users can't send messages). + /// Not for FeedChannel void onChannelFrozen(BaseChannel channel) {} /// A callback for when channel is unfrozen (Users can send messages). + /// Not for FeedChannel void onChannelUnfrozen(BaseChannel channel) {} /// A callback for when channel meta data is created. + /// Not for FeedChannel void onMetaDataCreated(BaseChannel channel, Map metaData) {} /// A callback for when channel meta data is updated. + /// Not for FeedChannel void onMetaDataUpdated(BaseChannel channel, Map metaData) {} /// A callback for when channel meta data is deleted. + /// Not for FeedChannel void onMetaDataDeleted(BaseChannel channel, List metaDataKeys) {} /// A callback for when channel meta counters is created. + /// Not for FeedChannel void onMetaCountersCreated( BaseChannel channel, Map metaCounters) {} /// A callback for when channel meta counters is updated. + /// Not for FeedChannel void onMetaCountersUpdated( BaseChannel channel, Map metaCounters) {} /// A callback for when channel meta counters are deleted. + /// Not for FeedChannel void onMetaCountersDeleted( BaseChannel channel, List metaCounterKeys) {} /// A callback for when operators change in channel + /// Not for FeedChannel void onOperatorUpdated(BaseChannel channel) {} /// A callback for when the thread information is updated. + /// Not for FeedChannel void onThreadInfoUpdated(BaseChannel channel, ThreadInfoUpdateEvent event) {} } /// The GroupChannel handler. abstract class GroupChannelHandler extends BaseChannelHandler { /// A callback for when read receipts are updated on `GroupChannel`. - /// To use the updated read receipt, refer to[GroupChannelRead.getReadStatus], + /// To use the updated read receipt, refer to [GroupChannelRead.getReadStatus], /// [GroupChannelRead.getReadMembers], [GroupChannelRead.getUnreadMembers]. void onReadStatusUpdated(GroupChannel channel) {} @@ -146,3 +161,7 @@ abstract class OpenChannelHandler extends BaseChannelHandler { /// Called when one or more open channel's member counts are changed. void onChannelParticipantCountChanged(List channels) {} } + +/// The FeedChannel handler. +/// @since 4.0.3 +abstract class FeedChannelHandler extends BaseChannelHandler {} diff --git a/lib/src/public/main/handler/user_event_handler.dart b/lib/src/public/main/handler/user_event_handler.dart index 1081d20c..4b29e364 100644 --- a/lib/src/public/main/handler/user_event_handler.dart +++ b/lib/src/public/main/handler/user_event_handler.dart @@ -1,7 +1,10 @@ // 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/channel/group_channel/group_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/model/message/unread_message_count.dart'; /// User event handler. /// To add or remove this handler, @@ -12,8 +15,16 @@ abstract class UserEventHandler { /// 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 customTypesCount, + 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 + void onTotalUnreadMessageCountChanged( + UnreadMessageCount unreadMessageCount, ) {} } diff --git a/lib/src/public/main/model/channel/feed_channel_change_logs.dart b/lib/src/public/main/model/channel/feed_channel_change_logs.dart new file mode 100644 index 00000000..b680d655 --- /dev/null +++ b/lib/src/public/main/model/channel/feed_channel_change_logs.dart @@ -0,0 +1,46 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +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/network/http/http_client/response/responses.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_channel.dart'; + +part 'feed_channel_change_logs.g.dart'; + +/// The FeedChannelChangeLogs class. +@JsonSerializable(createToJson: false) +class FeedChannelChangeLogs { + /// The updated channels. + @JsonKey(defaultValue: [], name: 'updated') + @FeedChannelConverter() + final List updatedChannels; + + /// The deleted channel urls. + @JsonKey(defaultValue: [], name: 'deleted') + final List deletedChannelUrls; + + /// True if it has more changelogs. + @JsonKey(defaultValue: false) + final bool hasMore; + + /// [token] to get next changelogs. + @JsonKey(name: 'next') + final String? token; + + FeedChannelChangeLogs({ + required this.updatedChannels, + required this.deletedChannelUrls, + required this.hasMore, + this.token, + }); + + static FeedChannelChangeLogs fromJsonWithChat( + Chat chat, Map json) { + final res = _$FeedChannelChangeLogsFromJson(json); + + for (final channel in res.updatedChannels) { + channel.set(chat); + } + return res; + } +} diff --git a/lib/src/public/main/model/channel/feed_channel_change_logs.g.dart b/lib/src/public/main/model/channel/feed_channel_change_logs.g.dart new file mode 100644 index 00000000..0fb68b30 --- /dev/null +++ b/lib/src/public/main/model/channel/feed_channel_change_logs.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'feed_channel_change_logs.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeedChannelChangeLogs _$FeedChannelChangeLogsFromJson( + Map json) => + FeedChannelChangeLogs( + updatedChannels: (json['updated'] as List?) + ?.map((e) => const FeedChannelConverter().fromJson(e as Object)) + .toList() ?? + [], + deletedChannelUrls: (json['deleted'] as List?) + ?.map((e) => e as String) + .toList() ?? + [], + hasMore: json['has_more'] as bool? ?? false, + token: json['next'] as String?, + ); diff --git a/lib/src/public/main/model/chat/global_notification_channel_setting.dart b/lib/src/public/main/model/chat/global_notification_channel_setting.dart new file mode 100644 index 00000000..b6d38eba --- /dev/null +++ b/lib/src/public/main/model/chat/global_notification_channel_setting.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; + +/// Class to obtain the global notification channel setting. +/// @since 4.0.3 +class GlobalNotificationChannelSetting { + /// The setting map received from the [SendbirdChat.getGlobalNotificationChannelSetting]. + /// @since 4.0.3 + final Map setting; + + GlobalNotificationChannelSetting({ + required this.setting, + }); +} diff --git a/lib/src/public/main/model/chat/notification_template.dart b/lib/src/public/main/model/chat/notification_template.dart new file mode 100644 index 00000000..e78e2d06 --- /dev/null +++ b/lib/src/public/main/model/chat/notification_template.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +/// Class to obtain the Notification template. +/// @since 4.0.3 +class NotificationTemplate { + /// The notification template. + /// @since 4.0.3 + final Map template; + + NotificationTemplate({ + required this.template, + }); +} diff --git a/lib/src/public/main/model/chat/notification_template_list.dart b/lib/src/public/main/model/chat/notification_template_list.dart new file mode 100644 index 00000000..7c36fa16 --- /dev/null +++ b/lib/src/public/main/model/chat/notification_template_list.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; + +/// The notification template list received from the [SendbirdChat.getNotificationTemplateListByToken]. +/// @since 4.0.3 +class NotificationTemplateList { + /// The notification template list. + /// @since 4.0.3 + final Map templateList; + + /// Returned true if it has more template list. + /// @since 4.0.3 + final bool hasMore; + + /// Returned token to get next template list. + /// @since 4.0.3 + final String? token; + + NotificationTemplateList({ + required this.templateList, + required this.hasMore, + required this.token, + }); +} diff --git a/lib/src/public/main/model/info/app_info.dart b/lib/src/public/main/model/info/app_info.dart index 66332891..8b49aba2 100644 --- a/lib/src/public/main/model/info/app_info.dart +++ b/lib/src/public/main/model/info/app_info.dart @@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; +import 'package:sendbird_chat_sdk/src/public/main/model/info/notification_info.dart'; part 'app_info.g.dart'; @@ -9,30 +10,36 @@ part 'app_info.g.dart'; /// The values for this will be set after a connection has been made. @JsonSerializable(createToJson: false) class AppInfo { - /// The current registered emoji version hash. - final String emojiHash; + /// List of all premium features that application is using. + @JsonKey(defaultValue: []) + final List premiumFeatureList; /// The maximum limit of file size for uploading. @JsonKey(defaultValue: 30 * 1024 * 1024) // Check final int uploadSizeLimit; - /// List of all premium features that application is using. + /// List of all attributes that the application is using. @JsonKey(defaultValue: []) - final List premiumFeatureList; + final List attributesInUse; + + /// The current registered emoji version hash. + final String emojiHash; /// Whether an application is using the reaction feature. final bool useReaction; - /// List of all attributes that the application is using. - @JsonKey(defaultValue: []) - final List attributesInUse; + /// Notification info. + /// @since 4.0.3 + @JsonKey(name: 'notifications') + final NotificationInfo? notificationInfo; AppInfo({ - required this.emojiHash, - required this.uploadSizeLimit, required this.premiumFeatureList, - required this.useReaction, + required this.uploadSizeLimit, required this.attributesInUse, + required this.emojiHash, + required this.useReaction, + this.notificationInfo, }); /// Checks whether the emoji list needs to be updated. diff --git a/lib/src/public/main/model/info/app_info.g.dart b/lib/src/public/main/model/info/app_info.g.dart index 913f5460..61ea0f0c 100644 --- a/lib/src/public/main/model/info/app_info.g.dart +++ b/lib/src/public/main/model/info/app_info.g.dart @@ -7,15 +7,19 @@ part of 'app_info.dart'; // ************************************************************************** AppInfo _$AppInfoFromJson(Map json) => AppInfo( - emojiHash: json['emoji_hash'] as String, - uploadSizeLimit: json['upload_size_limit'] as int? ?? 31457280, premiumFeatureList: (json['premium_feature_list'] as List?) ?.map((e) => e as String) .toList() ?? [], - useReaction: json['use_reaction'] as bool, + uploadSizeLimit: json['upload_size_limit'] as int? ?? 31457280, attributesInUse: (json['attributes_in_use'] as List?) ?.map((e) => e as String) .toList() ?? [], + emojiHash: json['emoji_hash'] as String, + useReaction: json['use_reaction'] as bool, + notificationInfo: json['notifications'] == null + ? null + : NotificationInfo.fromJson( + json['notifications'] as Map), ); diff --git a/lib/src/public/main/model/info/file_info.dart b/lib/src/public/main/model/info/file_info.dart index f16dc73c..be3c46fb 100644 --- a/lib/src/public/main/model/info/file_info.dart +++ b/lib/src/public/main/model/info/file_info.dart @@ -10,7 +10,7 @@ part 'file_info.g.dart'; @JsonSerializable() class FileInfo { @JsonKey(includeFromJson: false, includeToJson: false) - final File? file; + File? file; @JsonKey(includeFromJson: false, includeToJson: false) Uint8List? fileBytes; @@ -68,5 +68,6 @@ class FileInfo { Map toJson() => _$FileInfoToJson(this); bool get hasBinary => file != null || fileBytes != null; - bool get hasSource => file != null || (fileUrl != null && fileUrl != '') || fileBytes != null; + bool get hasSource => + file != null || (fileUrl != null && fileUrl != '') || fileBytes != null; } diff --git a/lib/src/public/main/model/info/notification_info.dart b/lib/src/public/main/model/info/notification_info.dart new file mode 100644 index 00000000..b7e9379a --- /dev/null +++ b/lib/src/public/main/model/info/notification_info.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; + +part 'notification_info.g.dart'; + +/// Represents information about Notifications. +/// @since 4.0.3 +@JsonSerializable(createToJson: false) +class NotificationInfo { + /// Whether notification is enabled + /// @since 4.0.3 + @JsonKey(name: 'enabled') + final bool isEnabled; + + /// List of feed channels for [SendbirdChat.currentUser], channel_key : channel_url + /// @since 4.0.3 + final Map feedChannels; + + /// Updated at for the global notification settings + /// @since 4.0.3 + final int settingsUpdatedAt; + + /// Token for the notification template list + /// @since 4.0.3 + final String? templateListToken; + + NotificationInfo({ + required this.isEnabled, + required this.feedChannels, + required this.settingsUpdatedAt, + this.templateListToken, + }); + + static NotificationInfo fromJson(Map json) => + _$NotificationInfoFromJson(json); +} diff --git a/lib/src/public/main/model/info/notification_info.g.dart b/lib/src/public/main/model/info/notification_info.g.dart new file mode 100644 index 00000000..08eced89 --- /dev/null +++ b/lib/src/public/main/model/info/notification_info.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NotificationInfo _$NotificationInfoFromJson(Map json) => + NotificationInfo( + isEnabled: json['enabled'] as bool, + feedChannels: Map.from(json['feed_channels'] as Map), + settingsUpdatedAt: json['settings_updated_at'] as int, + templateListToken: json['template_list_token'] as String?, + ); diff --git a/lib/src/public/main/model/message/unread_message_count.dart b/lib/src/public/main/model/message/unread_message_count.dart new file mode 100644 index 00000000..d73d9dea --- /dev/null +++ b/lib/src/public/main/model/message/unread_message_count.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/chat/sendbird_chat.dart'; + +/// The UnreadMessageCount class for [SendbirdChat.getTotalUnreadMessageCount]. +/// @since 4.0.3 +class UnreadMessageCount { + /// The total count of unread message count in all of group channels. + final int totalCountForGroupChannels; + + /// The total count of unread message count in all of feed channels. + final int totalCountForFeedChannels; + + /// Gets the number of unread message with subscribed custom type. + Map totalCountByCustomType; + + UnreadMessageCount({ + required this.totalCountForGroupChannels, + required this.totalCountForFeedChannels, + required this.totalCountByCustomType, + }); +} diff --git a/lib/src/public/main/model/reaction/reaction_event.g.dart b/lib/src/public/main/model/reaction/reaction_event.g.dart index 78bba4d5..25758453 100644 --- a/lib/src/public/main/model/reaction/reaction_event.g.dart +++ b/lib/src/public/main/model/reaction/reaction_event.g.dart @@ -23,6 +23,7 @@ ReactionEvent _$ReactionEventFromJson(Map json) => const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$ReactionEventActionEnumMap = { diff --git a/lib/src/public/main/model/thread/thread_info_updated_event.g.dart b/lib/src/public/main/model/thread/thread_info_updated_event.g.dart index f544aba8..22072f1a 100644 --- a/lib/src/public/main/model/thread/thread_info_updated_event.g.dart +++ b/lib/src/public/main/model/thread/thread_info_updated_event.g.dart @@ -19,4 +19,5 @@ ThreadInfoUpdateEvent _$ThreadInfoUpdateEventFromJson( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; diff --git a/lib/src/public/main/params/channel/feed_channel_change_logs_params.dart b/lib/src/public/main/params/channel/feed_channel_change_logs_params.dart new file mode 100644 index 00000000..10f2c457 --- /dev/null +++ b/lib/src/public/main/params/channel/feed_channel_change_logs_params.dart @@ -0,0 +1,16 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +/// Represents a feed channel change logs params. +/// @since 4.0.3 +class FeedChannelChangeLogsParams { + /// Determines whether to include empty channels or not (channels without messages). + /// Defaults to true. + /// @since 4.0.3 + bool includeEmpty = true; + + Map toJson() { + return { + 'show_empty': includeEmpty, + }; + } +} diff --git a/lib/src/public/main/params/channel/group_channel_change_logs_params.dart b/lib/src/public/main/params/channel/group_channel_change_logs_params.dart index 9a3605c5..376216f8 100644 --- a/lib/src/public/main/params/channel/group_channel_change_logs_params.dart +++ b/lib/src/public/main/params/channel/group_channel_change_logs_params.dart @@ -16,11 +16,16 @@ class GroupChannelChangeLogsParams { /// Defaults to true. bool includeFrozen = true; + /// Whether to include chat notification channels in changelogs. + /// @since 4.0.3 + bool includeChatNotification = false; + Map toJson() { return { 'custom_type': customTypes, 'show_empty': includeEmpty, 'show_frozen': includeFrozen, + 'include_chat_notification': includeChatNotification, }; } } diff --git a/lib/src/public/main/params/message/base_message_create_params.dart b/lib/src/public/main/params/message/base_message_create_params.dart index 901f0193..83b83b8b 100644 --- a/lib/src/public/main/params/message/base_message_create_params.dart +++ b/lib/src/public/main/params/message/base_message_create_params.dart @@ -49,15 +49,13 @@ class BaseMessageCreateParams { }); /// withMessage - BaseMessageCreateParams.withMessage(BaseMessage message, {bool? deepCopy}) { + BaseMessageCreateParams.withMessage(BaseMessage message) { data = message.data; customType = message.customType; mentionType = message.mentionType ?? MentionType.users; mentionedUserIds = message.mentionedUsers.map((e) => e.userId).toList(); metaArrays = message.allMetaArrays; - if (deepCopy != null && deepCopy) { - parentMessageId = message.parentMessageId; - } + parentMessageId = message.parentMessageId; replyToChannel = message.isReplyToChannel; } diff --git a/lib/src/public/main/params/message/file_message_create_params.dart b/lib/src/public/main/params/message/file_message_create_params.dart index 97d7e645..9fe4868b 100644 --- a/lib/src/public/main/params/message/file_message_create_params.dart +++ b/lib/src/public/main/params/message/file_message_create_params.dart @@ -148,12 +148,21 @@ class FileMessageCreateParams extends BaseMessageCreateParams { ); /// withMessage - FileMessageCreateParams.withMessage(FileMessage fileMessage, {bool? deepCopy}) - : fileInfo = FileInfo.fromFileUrl( - fileUrl: fileMessage.url, - mimeType: fileMessage.type, - ), - super.withMessage(fileMessage, deepCopy: deepCopy); + FileMessageCreateParams.withMessage(FileMessage fileMessage) + : super.withMessage(fileMessage) { + if (fileMessage.url.isNotEmpty) { + fileInfo = FileInfo.fromFileUrl( + fileUrl: fileMessage.url, + mimeType: fileMessage.type, + ); + } else if (fileMessage.file != null) { + fileInfo = FileInfo.fromFile( + file: fileMessage.file, + fileName: fileMessage.name, + mimeType: fileMessage.type, + ); + } + } @override Map toJson() { diff --git a/lib/src/public/main/params/message/message_list_params.g.dart b/lib/src/public/main/params/message/message_list_params.g.dart index 4a2b5e5b..5f0d515d 100644 --- a/lib/src/public/main/params/message/message_list_params.g.dart +++ b/lib/src/public/main/params/message/message_list_params.g.dart @@ -40,7 +40,7 @@ Map _$MessageListParamsToJson(MessageListParams instance) => 'next_limit': instance.nextResultSize, 'include': instance.inclusive, 'reverse': instance.reverse, - 'message_type': _$MessageTypeFilterEnumMap[instance.messageType], + 'message_type': _$MessageTypeFilterEnumMap[instance.messageType]!, 'custom_types': instance.customTypes, 'sender_ids': instance.senderIds, 'show_subchannel_messages_only': instance.showSubChannelMessagesOnly, @@ -53,7 +53,7 @@ const _$ReplyTypeEnumMap = { }; const _$MessageTypeFilterEnumMap = { - MessageTypeFilter.all: null, + MessageTypeFilter.all: '', MessageTypeFilter.user: 'MESG', MessageTypeFilter.file: 'FILE', MessageTypeFilter.admin: 'ADMN', diff --git a/lib/src/public/main/params/message/message_retrieval_params.g.dart b/lib/src/public/main/params/message/message_retrieval_params.g.dart index b65731a9..49b64892 100644 --- a/lib/src/public/main/params/message/message_retrieval_params.g.dart +++ b/lib/src/public/main/params/message/message_retrieval_params.g.dart @@ -36,6 +36,7 @@ Map _$MessageRetrievalParamsToJson( const _$ChannelTypeEnumMap = { ChannelType.group: 'group', ChannelType.open: 'open', + ChannelType.feed: 'feed', }; const _$ReplyTypeEnumMap = { diff --git a/lib/src/public/main/params/message/threaded_message_list_params.g.dart b/lib/src/public/main/params/message/threaded_message_list_params.g.dart index 9afa3b3e..266dcf36 100644 --- a/lib/src/public/main/params/message/threaded_message_list_params.g.dart +++ b/lib/src/public/main/params/message/threaded_message_list_params.g.dart @@ -38,7 +38,7 @@ Map _$ThreadedMessageListParamsToJson( 'next_limit': instance.nextResultSize, 'include': instance.inclusive, 'reverse': instance.reverse, - 'message_type': _$MessageTypeFilterEnumMap[instance.messageType], + 'message_type': _$MessageTypeFilterEnumMap[instance.messageType]!, 'custom_type': instance.customType, 'sender_ids': instance.senderIds, }; @@ -50,7 +50,7 @@ const _$ReplyTypeEnumMap = { }; const _$MessageTypeFilterEnumMap = { - MessageTypeFilter.all: null, + MessageTypeFilter.all: '', MessageTypeFilter.user: 'MESG', MessageTypeFilter.file: 'FILE', MessageTypeFilter.admin: 'ADMN', diff --git a/lib/src/public/main/params/message/user_message_create_params.dart b/lib/src/public/main/params/message/user_message_create_params.dart index 869e88c7..a74ba290 100644 --- a/lib/src/public/main/params/message/user_message_create_params.dart +++ b/lib/src/public/main/params/message/user_message_create_params.dart @@ -49,11 +49,12 @@ class UserMessageCreateParams extends BaseMessageCreateParams { ); /// withMessage - UserMessageCreateParams.withMessage(UserMessage userMessage, {bool? deepCopy}) + UserMessageCreateParams.withMessage(UserMessage userMessage) : message = userMessage.message, translationTargetLanguages = userMessage.translations.keys.toList(), pollId = userMessage.poll?.id, - super.withMessage(userMessage, deepCopy: deepCopy); + extendedMessage = userMessage.extendedMessage, + super.withMessage(userMessage); @override Map toJson() { diff --git a/lib/src/public/main/params/notifications/notification_template_list_params.dart b/lib/src/public/main/params/notifications/notification_template_list_params.dart new file mode 100644 index 00000000..062b8488 --- /dev/null +++ b/lib/src/public/main/params/notifications/notification_template_list_params.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/public/main/query/base_query.dart'; + +/// Params for retrieving Notification template list. +/// @since 4.0.3 +class NotificationTemplateListParams { + /// The key filter to retrieve only selected templates with given keys. Defaults to null. + /// @since 4.0.3 + final List? keys; + + /// Whether the result is set to be reversed or not. Defaults to false. + /// @since 4.0.3 + final bool reverse; + + /// The maximum number of items per queried page. + /// @since 4.0.3 + final int limit; + + NotificationTemplateListParams({ + this.keys, + this.reverse = false, + this.limit = BaseQuery.defaultQueryLimit, + }); +} diff --git a/lib/src/public/main/query/channel/feed_channel_list_query.dart b/lib/src/public/main/query/channel/feed_channel_list_query.dart new file mode 100644 index 00000000..d1d84ac6 --- /dev/null +++ b/lib/src/public/main/query/channel/feed_channel_list_query.dart @@ -0,0 +1,65 @@ +// 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/main/extensions/extensions.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/feed_channel/feed_channel_list_request.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/response/responses.dart'; +import 'package:sendbird_chat_sdk/src/public/core/channel/feed_channel/feed_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/exceptions.dart'; +import 'package:sendbird_chat_sdk/src/public/main/query/base_query.dart'; + +/// A class representing query to retrieve [FeedChannel] list for the current [User]. +/// @since 4.0.3 +class FeedChannelListQuery extends BaseQuery { + /// Checks whether query result includes empty channels. (channels without messages). + /// This flag is true by default. + /// @since 4.0.3 + bool includeEmpty = true; + + FeedChannelListQuery({ + Chat? chat, + }) : super(chat: chat ?? SendbirdChat().chat); + + /// Gets the list of [FeedChannel]s. The queried result is passed to `handler` as list. + /// If this method is repeatedly called after each [next] is finished, + /// it retrieves the following pages of the [FeedChannel] list. + /// If there is no more pages to be read, an empty list (not `null`) is returned. + /// @since 4.0.3 + @override + Future> next() async { + sbLog.i(StackTrace.current); + + if (isLoading) throw QueryInProgressException(); + if (!hasNext) return []; + + isLoading = true; + + final options = [ + if (includeEmpty) ChannelListQueryIncludeOption.includeEmpty, + ChannelListQueryIncludeOption.includeMember, + ChannelListQueryIncludeOption.includeReadReceipt, + ChannelListQueryIncludeOption.includeDeliveryReceipt, + ]; + + final res = await chat.apiClient.send( + FeedChannelListRequest( + chat, + limit: limit, + options: options, + token: token, + ), + ); + + for (final element in res.channels) { + element.set(chat); + } + + isLoading = false; + token = res.next; + hasNext = res.next != ''; + return res.channels; + } +} diff --git a/lib/src/public/main/query/channel/group_channel_list_query.dart b/lib/src/public/main/query/channel/group_channel_list_query.dart index 57530f7e..717053c9 100644 --- a/lib/src/public/main/query/channel/group_channel_list_query.dart +++ b/lib/src/public/main/query/channel/group_channel_list_query.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/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/enum_utils.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/group_channel_list_request.dart'; @@ -107,6 +108,10 @@ class GroupChannelListQuery extends BaseQuery { /// This flag is true by default. bool includeMetaData = true; + /// Whether to include chat notification [GroupChannel]. + /// @since 4.0.3 + bool includeChatNotification = false; + GroupChannelListQuery({ Chat? chat, }) : super(chat: chat ?? SendbirdChat().chat); @@ -222,6 +227,8 @@ class GroupChannelListQuery extends BaseQuery { if (includeEmpty) ChannelListQueryIncludeOption.includeEmpty, if (includeFrozen) ChannelListQueryIncludeOption.includeFrozen, if (includeMetaData) ChannelListQueryIncludeOption.includeMetadata, + if (includeChatNotification) + ChannelListQueryIncludeOption.includeChatNotification, ChannelListQueryIncludeOption.includeMember, ChannelListQueryIncludeOption.includeReadReceipt, ChannelListQueryIncludeOption.includeDeliveryReceipt, diff --git a/lib/src/public/main/query/channel/open_channel_list_query.dart b/lib/src/public/main/query/channel/open_channel_list_query.dart index 564205e8..5a7ce308 100644 --- a/lib/src/public/main/query/channel/open_channel_list_query.dart +++ b/lib/src/public/main/query/channel/open_channel_list_query.dart @@ -1,12 +1,12 @@ // 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/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/open_channel/open_channel_list_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/response/responses.dart'; import 'package:sendbird_chat_sdk/src/public/core/channel/open_channel/open_channel.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/query/base_query.dart'; diff --git a/lib/src/public/main/query/channel/public_group_channel_list_query.dart b/lib/src/public/main/query/channel/public_group_channel_list_query.dart index d0cd19a6..29f9b6ed 100644 --- a/lib/src/public/main/query/channel/public_group_channel_list_query.dart +++ b/lib/src/public/main/query/channel/public_group_channel_list_query.dart @@ -1,6 +1,7 @@ // 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/main/extensions/extensions.dart'; import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/channel/group_channel/public/public_group_channel_list_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/response/responses.dart'; diff --git a/lib/src/public/main/query/message/scheduled_message_list_query.dart b/lib/src/public/main/query/message/scheduled_message_list_query.dart index 617d9c34..c3fd630e 100644 --- a/lib/src/public/main/query/message/scheduled_message_list_query.dart +++ b/lib/src/public/main/query/message/scheduled_message_list_query.dart @@ -20,7 +20,7 @@ class ScheduledMessageListQuery extends BaseQuery { this.params, Chat? chat, }) : super(chat: chat ?? SendbirdChat().chat) { - if (params?.limit != null) limit = (params?.limit)!; + if (params?.limit != null) limit = params!.limit!; } /// Gets the list of next items. diff --git a/pubspec.yaml b/pubspec.yaml index b5dd889e..81276cf9 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.2 +version: 4.0.3 homepage: https://sendbird.com repository: https://github.com/sendbird/sendbird-chat-sdk-flutter documentation: https://sendbird.com/docs/chat/v4/flutter/getting-started/send-first-message @@ -25,7 +25,7 @@ dependencies: http: ^0.13.6 logger: ^1.4.0 mime: ^1.0.4 - connectivity_plus: ^2.3.9 + connectivity_plus: ^4.0.1 http_parser: ^4.0.2 web_socket_channel: ^2.4.0 universal_io: ^2.2.0