diff --git a/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs b/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs index 91442603..75f42418 100644 --- a/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs +++ b/Assets/Plugins/StreamChat/Core/Exceptions/StreamApiException.cs @@ -6,13 +6,69 @@ namespace StreamChat.Core.Exceptions { /// - /// Exception thrown when API request failed + /// Exception thrown when an API request is rejected by the server. Inspect + /// (HTTP status) and (Stream's numeric error code) to react to a specific failure. + /// The most common branches are exposed as helpers + /// (e.g. ). + /// See API Error Codes for the full list. /// public class StreamApiException : Exception { + // HTTP 400 — Bad Request + public const int InputErrorHttpStatusCode = 400; + public const int InputErrorStreamCode = 4; + + public const int MessageTooLongErrorHttpStatusCode = 400; + public const int MessageTooLongErrorStreamCode = 20; + + public const int MessageModerationFailedErrorHttpStatusCode = 400; + public const int MessageModerationFailedErrorStreamCode = 73; + + // HTTP 401 — Unauthorized + public const int AuthenticationErrorHttpStatusCode = 401; + public const int AuthenticationErrorStreamCode = 5; + + public const int TokenExpiredErrorHttpStatusCode = 401; + public const int TokenExpiredErrorStreamCode = 40; + + public const int TokenNotValidYetErrorHttpStatusCode = 401; + public const int TokenNotValidYetErrorStreamCode = 41; + + public const int TokenBeforeIssuedAtErrorHttpStatusCode = 401; + public const int TokenBeforeIssuedAtErrorStreamCode = 42; + + public const int TokenSignatureInvalidErrorHttpStatusCode = 401; + public const int TokenSignatureInvalidErrorStreamCode = 43; + + // HTTP 403 — Forbidden + public const int PermissionDeniedErrorHttpStatusCode = 403; + public const int PermissionDeniedErrorStreamCode = 17; + + public const int CooldownErrorHttpStatusCode = 403; + public const int CooldownErrorStreamCode = 60; + + public const int NoAccessToChannelsErrorHttpStatusCode = 403; + public const int NoAccessToChannelsErrorStreamCode = 70; + + public const int AppSuspendedErrorHttpStatusCode = 403; + public const int AppSuspendedErrorStreamCode = 99; + + // HTTP 404 — Not Found + public const int DoesNotExistErrorHttpStatusCode = 404; + public const int DoesNotExistErrorStreamCode = 16; + + // HTTP 413 — Payload Too Large + public const int PayloadTooBigErrorHttpStatusCode = 413; + public const int PayloadTooBigErrorStreamCode = 22; + + // HTTP 429 — Too Many Requests public const int RateLimitErrorHttpStatusCode = 429; public const int RateLimitErrorStreamCode = 9; + // HTTP 500 — Internal Server Error + public const int InternalSystemErrorHttpStatusCode = 500; + public const int InternalSystemErrorStreamCode = -1; + //Stream public int? StatusCode { get; } public int? Code { get; } @@ -72,12 +128,128 @@ private static string PrintExceptionFields(APIErrorInternalDTO apiError) } /// - /// Extensions for + /// Helpers for branching on the most common cases. Each + /// helper matches both the HTTP status and the Stream numeric error code so it stays + /// correct if the API ever reuses a status code for a different error. /// public static class StreamApiExceptionExtensions { + /// + /// HTTP 429 / Stream code 9. The caller exceeded the rate limit. Back off (typically with + /// exponential delay) before retrying. + /// public static bool IsRateLimitExceededError(this StreamApiException streamApiException) => streamApiException.Code == StreamApiException.RateLimitErrorStreamCode && streamApiException.StatusCode == StreamApiException.RateLimitErrorHttpStatusCode; + + /// + /// HTTP 403 / Stream code 60. The user sent a message while the channel's slow-mode cooldown + /// was active. Read IStreamChannel.Cooldown (in seconds) and gate the send UI for that + /// many seconds before allowing another send. + /// + public static bool IsCooldownError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.CooldownErrorStreamCode && + streamApiException.StatusCode == StreamApiException.CooldownErrorHttpStatusCode; + + /// + /// HTTP 403 / Stream code 17. The user lacks permission for the attempted action. Hide or + /// disable the corresponding UI control; do not retry. + /// + public static bool IsPermissionDeniedError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.PermissionDeniedErrorStreamCode && + streamApiException.StatusCode == StreamApiException.PermissionDeniedErrorHttpStatusCode; + + /// + /// HTTP 403 / Stream code 70. The user has no access to the requested channel(s). Hide the + /// channel from the local list or show an "access denied" placeholder. + /// + public static bool IsNoAccessToChannelsError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.NoAccessToChannelsErrorStreamCode && + streamApiException.StatusCode == StreamApiException.NoAccessToChannelsErrorHttpStatusCode; + + /// + /// HTTP 403 / Stream code 99. The Stream application has been suspended. Surface a + /// service-unavailable UI; the integration is offline until the app is reactivated. + /// + public static bool IsAppSuspendedError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.AppSuspendedErrorStreamCode && + streamApiException.StatusCode == StreamApiException.AppSuspendedErrorHttpStatusCode; + + /// + /// HTTP 401 / Stream code 5. Generic authentication failure (bad / missing token, wrong API + /// key, etc.). If you connected with an ITokenProvider the SDK refreshes automatically; + /// otherwise reconnect with a fresh token. + /// + public static bool IsAuthenticationError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.AuthenticationErrorStreamCode && + streamApiException.StatusCode == StreamApiException.AuthenticationErrorHttpStatusCode; + + /// + /// HTTP 401 / Stream code 40. The user token has expired. With an ITokenProvider + /// configured the SDK refreshes the token automatically; otherwise reconnect with a fresh + /// token from your backend. + /// + public static bool IsTokenExpiredError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.TokenExpiredErrorStreamCode && + streamApiException.StatusCode == StreamApiException.TokenExpiredErrorHttpStatusCode; + + /// + /// True for any of the token-related auth errors (expired / not-valid-yet / before-issued-at / + /// signature-invalid). Use this when you do not need to distinguish between them. + /// + public static bool IsTokenError(this StreamApiException streamApiException) + => streamApiException.StatusCode == 401 && + (streamApiException.Code == StreamApiException.TokenExpiredErrorStreamCode || + streamApiException.Code == StreamApiException.TokenNotValidYetErrorStreamCode || + streamApiException.Code == StreamApiException.TokenBeforeIssuedAtErrorStreamCode || + streamApiException.Code == StreamApiException.TokenSignatureInvalidErrorStreamCode); + + /// + /// HTTP 404 / Stream code 16. The targeted resource (channel, message, user, …) does not + /// exist or was deleted. Refresh the local view; do not retry. + /// + public static bool IsDoesNotExistError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.DoesNotExistErrorStreamCode && + streamApiException.StatusCode == StreamApiException.DoesNotExistErrorHttpStatusCode; + + /// + /// HTTP 400 / Stream code 4. The request payload was invalid. Inspect + /// ExceptionFields for per-field validation messages and surface them to the user. + /// + public static bool IsInputError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.InputErrorStreamCode && + streamApiException.StatusCode == StreamApiException.InputErrorHttpStatusCode; + + /// + /// HTTP 400 / Stream code 20. The outgoing message exceeded the channel-type's + /// max_message_length. Show a character-limit error to the sender. + /// + public static bool IsMessageTooLongError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.MessageTooLongErrorStreamCode && + streamApiException.StatusCode == StreamApiException.MessageTooLongErrorHttpStatusCode; + + /// + /// HTTP 400 / Stream code 73. The message was rejected by moderation. Show a "your message + /// was filtered" UI to the sender; do not retry the identical payload. + /// + public static bool IsMessageModerationFailedError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.MessageModerationFailedErrorStreamCode && + streamApiException.StatusCode == StreamApiException.MessageModerationFailedErrorHttpStatusCode; + + /// + /// HTTP 413 / Stream code 22. The uploaded payload (e.g. file / image attachment) exceeded + /// the maximum allowed size. Ask the user to choose a smaller file. + /// + public static bool IsPayloadTooBigError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.PayloadTooBigErrorStreamCode && + streamApiException.StatusCode == StreamApiException.PayloadTooBigErrorHttpStatusCode; + + /// + /// HTTP 500 / Stream code -1. The server failed for an unspecified reason. Safe to retry + /// after a short delay; surface a transient-failure UI if it persists. + /// + public static bool IsInternalSystemError(this StreamApiException streamApiException) + => streamApiException.Code == StreamApiException.InternalSystemErrorStreamCode && + streamApiException.StatusCode == StreamApiException.InternalSystemErrorHttpStatusCode; } -} \ No newline at end of file +} diff --git a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs index 92d97fec..150d8776 100644 --- a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs +++ b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs @@ -399,7 +399,7 @@ Task BanMemberAsync(IStreamChannelMember member, string reason = "", /// [Optional] reason description why user got banned /// [Optional] timeout in minutes after which ban is automatically expired /// [Optional] Should ban apply to user's IP address - /// https://getstream.io/chat/docs/unity/moderation/?language=unreal#shadow-ban + /// https://getstream.io/chat/docs/unity/moderation/?language=unity#shadow-ban Task ShadowBanUserAsync(IStreamUser user, string reason = "", int? timeoutMinutes = default, bool isIpBan = false); @@ -411,7 +411,7 @@ Task ShadowBanUserAsync(IStreamUser user, string reason = "", /// [Optional] reason description why user got banned /// [Optional] timeout in minutes after which ban is automatically expired /// [Optional] Should ban apply to user's IP address - /// https://getstream.io/chat/docs/unity/moderation/?language=unreal#shadow-ban + /// https://getstream.io/chat/docs/unity/moderation/?language=unity#shadow-ban Task ShadowBanMemberAsync(IStreamChannelMember member, string reason = "", int? timeoutMinutes = default, bool isIpBan = false); diff --git a/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs index 95ee5944..301beb66 100644 --- a/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/ChannelsCodeSamples.cs @@ -28,7 +28,7 @@ public async Task CreateChannelUsingId() } /// - /// https://getstream.io/chat/docs/unity/creating_channels/?language=unreal#2.-creating-a-channel-for-a-list-of-members + /// https://getstream.io/chat/docs/unity/creating_channels/?language=unity#2.-creating-a-channel-for-a-list-of-members /// public async Task CreateChannelForListOfMembers() { @@ -52,7 +52,7 @@ public async Task CreateChannelForListOfMembers() #region Watch a channel /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#to-start-watching-a-channel + /// https://getstream.io/chat/docs/unity/creating-channels/?language=unity#watching-channels /// public async Task StartWatchingChannel() { @@ -60,9 +60,9 @@ public async Task StartWatchingChannel() { UserFilter.Id.EqualsTo("other-user-id") }; -// find user you want to start chat with - var users = await Client.QueryUsersAsync(filters); +// Find user you want to start chat with + var users = await Client.QueryUsersAsync(filters); var otherUser = users.First(); var localUser = Client.LocalUserData.User; @@ -70,15 +70,12 @@ public async Task StartWatchingChannel() var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); // Get channel with users combination - var channelWithUsers - = await Client.GetOrCreateChannelWithMembersAsync(ChannelType.Messaging, - new[] { localUser, otherUser }); + var channelWithUsers = await Client.GetOrCreateChannelWithMembersAsync(ChannelType.Messaging, + new[] { localUser, otherUser }); // Access properties Debug.Log(channel.Name); Debug.Log(channel.Members); - Debug.Log(channel.Name); - Debug.Log(channel.Name); // Subscribe to events so you can react to updates channel.MessageReceived += OnMessageReceived; @@ -120,68 +117,61 @@ private void OnMessageReceived(IStreamChannel channel, IStreamMessage message) } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels + /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#channel-creation-and-watching /// public async Task WatchingMultipleChannels() { - var localUser = Client.LocalUserData.User; - var filters = new IFieldFilterRule[] { // Get channels where local user is a member - ChannelFilter.Members.In(localUser.Id) + ChannelFilter.Members.In(Client.LocalUserData.UserId) }; +// Get channel state or create one if it doesn't exist + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "general"); + +// Or get multiple channels matching a filter var channels = await Client.QueryChannelsAsync(filters); - // After query is done - loop channels and subscribe to events - foreach (var channel in channels) + // Subscribe to per-channel events as needed + foreach (var c in channels) { - // Access properties - Debug.Log(channel.Name); - Debug.Log(channel.Members); - Debug.Log(channel.Name); - Debug.Log(channel.Name); - - // Subscribe to events so you can react to updates - channel.MessageReceived += OnMessageReceived; - channel.MessageUpdated += OnMessageUpdated; - channel.MessageDeleted += OnMessageDeleted; - channel.ReactionAdded += OnReactionAdded; - channel.ReactionUpdated += OnReactionUpdated; - channel.ReactionRemoved += OnReactionRemoved; + c.MessageReceived += OnMessageReceived; + c.MessageUpdated += OnMessageUpdated; + c.MessageDeleted += OnMessageDeleted; + c.ReactionAdded += OnReactionAdded; + c.ReactionUpdated += OnReactionUpdated; + c.ReactionRemoved += OnReactionRemoved; } } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels + /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#sort-best-practices /// public async Task WatchingMultipleChannels2() { - var localUser = Client.LocalUserData.User; - var filters = new IFieldFilterRule[] { // Get channels where local user is a member - ChannelFilter.Members.In(localUser.Id) + ChannelFilter.Members.In(Client.LocalUserData.UserId) }; - // You can also sort by various fields var sort = ChannelSort.OrderByDescending(ChannelSortFieldName.LastMessageAt); var channels = await Client.QueryChannelsAsync(filters, sort); } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watching-multiple-channels + /// Code-only reference: no live Unity docs page demonstrates chained + /// channel sort (ThenByDescending) for any SDK, so this snippet + /// has no published home. Kept as a code-only example of how to chain + /// multiple sort fields against . /// public async Task WatchingMultipleChannels3() { - var localUser = Client.LocalUserData.User; - var filters = new IFieldFilterRule[] { // Get channels where local user is a member - ChannelFilter.Members.In(localUser.Id) + ChannelFilter.Members.In(Client.LocalUserData.UserId) }; // You can sort by multiple fields and chain as many ThenByDescending as you need @@ -192,7 +182,11 @@ public async Task WatchingMultipleChannels3() } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#stop-watching-a-channel + /// Code-only reference: creating-channels/?language=unity and + /// query-channels/?language=unity (the live targets of the old + /// watch_channel/ redirect) do not expose a "Stop watching a + /// channel" section for any SDK, so this snippet has no published home. + /// Kept as a code-only example of . /// public async Task StopWatchingChannel() { @@ -202,7 +196,11 @@ public async Task StopWatchingChannel() } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#watcher-count + /// Code-only reference: creating-channels/?language=unity and + /// query-channels/?language=unity (the live targets of the old + /// watch_channel/ redirect) do not surface a "Watcher count" + /// section for any SDK. Kept as a code-only example of how to read + /// . /// public async Task WatcherCount() { @@ -212,16 +210,12 @@ public async Task WatcherCount() } /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#paginating-channel-watchers-with-channel.query - /// - public async Task PaginateChannelWatchers() - { - //StreamTodo: IMPLEMENT watchers pagination - await Task.CompletedTask; - } - - /// - /// https://getstream.io/chat/docs/unity/watch_channel/?language=unity#listening-to-changes-in-watchers + /// Code-only reference: the old watch_channel/ page exposed + /// "Listening to changes in watchers" but the live targets of its + /// redirect (creating-channels/, query-channels/) do not + /// host a watcher-events section for any SDK. Kept as a code-only + /// example of / + /// . /// public async Task ListenToChangesInWatchers() { @@ -419,7 +413,7 @@ public async Task AddAndRemoveModeratorsToChanel() #region Querying Channels /// - /// https://getstream.io/chat/docs/unity/query_channels/?language=unity + /// https://getstream.io/chat/docs/unity/query-channels/?language=unity /// public async Task QueryChannels() { @@ -435,7 +429,12 @@ public async Task QueryChannels() } /// - /// https://getstream.io/chat/docs/unity/query_channels/?language=unity + /// Code-only reference: query-channels/?language=unity only + /// surfaces the single-filter Members.In(...) example (covered + /// by ); the extended + /// Members + Muted + MembersCount filter set is not published + /// for any SDK. Kept as a code-only example of combining multiple + /// rules in a single query. /// public async Task QueryChannelsExtended() { @@ -451,23 +450,25 @@ public async Task QueryChannelsExtended() } /// - /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#messaging-and-team + /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#messaging-and-team-channels /// public async Task MessagingAndTeam() { var filters = new List { - // Return only channels where local user is a member + ChannelFilter.Type.EqualsTo(ChannelType.Messaging), ChannelFilter.Members.In(Client.LocalUserData.UserId), - - // You can define multiple filters that will all have to be satisfied }; var channels = await Client.QueryChannelsAsync(filters); } /// - /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#support + /// Code-only reference: query-channels/?language=unity does + /// not host a "Support" / customer-service filter example for any + /// SDK (the #support anchor on the old query_channels/ + /// page no longer exists). Kept as a code-only example of querying + /// channels by custom fields via . /// public async Task Support() { @@ -482,7 +483,7 @@ public async Task Support() } /// - /// https://getstream.io/chat/docs/unity/query_channels/?language=unity#pagination + /// https://getstream.io/chat/docs/unity/query-channels/?language=unity#pagination /// public async Task QueryChannelsPagination() { @@ -490,13 +491,11 @@ public async Task QueryChannelsPagination() { // Return only channels where local user is a member ChannelFilter.Members.In(Client.LocalUserData.UserId), - - // You can define multiple filters that will all have to be satisfied }; -// Pass limit and offset to control the page or results returned -// Limit - how many records per page -// Offset - how many records to skip +// Pass limit and offset to control pagination +// Limit - records per page +// Offset - records to skip var channels = await Client.QueryChannelsAsync(filters, limit: 30, offset: 60); } @@ -532,7 +531,7 @@ public async Task QueryMembers() #region Channel Pagination /// - /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity + /// https://getstream.io/chat/docs/unity/channel-pagination/?language=unity#message-pagination /// public async Task ChannelPaginateMessages() { @@ -543,30 +542,41 @@ public async Task ChannelPaginateMessages() await channel.LoadOlderMessagesAsync(); } - #endregion - /// - /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity + /// https://getstream.io/chat/docs/unity/channel-pagination/?language=unity#member-and-watcher-pagination /// - public async Task ChannelPaginateMembers() + public async Task ChannelPaginateMembersAndWatchers() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); - // StreamTodo: IMPLEMENT channel members pagination - } +// channel.Members exposes the loaded members snapshot (max 100 by default) + foreach (var member in channel.Members) + { + // ... + } - /// - /// https://getstream.io/chat/docs/unity/channel_pagination/?language=unity - /// - public async Task ChannelPaginateWatchers() - { - var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); +// channel.Watchers exposes the loaded watchers snapshot (max 100 by default) + foreach (var watcher in channel.Watchers) + { + // ... + } + +// Use QueryMembersAsync to page beyond the loaded snapshot + var filters = new Dictionary(); + var nextMembers = await channel.QueryMembersAsync(filters, limit: 20, offset: 0); - // StreamTodo: IMPLEMENT channel watchers pagination +// Paginating watchers beyond the loaded list is not yet available in the Unity SDK. +// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues } + #endregion + /// - /// https://getstream.io/chat/docs/unity/channel_capabilities/?language=unity + /// Code-only reference: there is no Unity tab on + /// https://getstream.io/chat/docs/unity/chat-permission-policies/?language=unity + /// (or any other live docs page) that demonstrates reading + /// . The capability strings + /// are listed on https://getstream.io/chat/docs/unity/permissions-reference/?language=unity. /// public async Task ChannelCapabilities() { @@ -576,8 +586,6 @@ public async Task ChannelCapabilities() { // User can update own message } - - // Check action keys here https://getstream.io/chat/docs/unity/permissions_reference/?language=unity } /// @@ -668,25 +676,48 @@ public async Task QueryPendingInvites() } /// - /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#muting-channels + /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#mute-a-channel /// public async Task MuteChannel() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); + +// Mute a channel for the current user await channel.MuteChannelAsync(); + +// Mute a channel for 2 weeks (duration in milliseconds) + await channel.MuteChannelAsync(milliseconds: 14 * 24 * 60 * 60 * 1000); + +// Mute a channel for 10 seconds + await channel.MuteChannelAsync(milliseconds: 10000); + +// The list of channel mutes (with expiration times) is available after connect + var channelMutes = Client.LocalUserData.ChannelMutes; } /// - /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#query-muted-channels + /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#query-muted-channels /// public async Task QueryMutedChannels() { - //StreamTodo: IMPLEMENT query muted channels - await Task.CompletedTask; +// Retrieve all channels excluding muted ones + var notMutedFilters = new IFieldFilterRule[] + { + ChannelFilter.Members.In(Client.LocalUserData.UserId), + ChannelFilter.Muted.EqualsTo(false), + }; + var notMutedChannels = await Client.QueryChannelsAsync(notMutedFilters); + +// Retrieve all muted channels + var mutedFilters = new IFieldFilterRule[] + { + ChannelFilter.Muted.EqualsTo(true), + }; + var mutedChannels = await Client.QueryChannelsAsync(mutedFilters); } /// - /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#remove-a-channel-mute + /// https://getstream.io/chat/docs/unity/muting-channels/?language=unity#remove-a-channel-mute /// public async Task RemoveChannelMute() { @@ -695,7 +726,7 @@ public async Task RemoveChannelMute() } /// - /// https://getstream.io/chat/docs/unity/muting_channels/?language=unity#hiding-a-channel + /// https://getstream.io/chat/docs/unity/hiding-channels/?language=unity#hide-a-channel /// public async Task HideAndShowChannel() { @@ -780,11 +811,22 @@ public async Task TruncateChannel() } /// - /// https://getstream.io/chat/docs/unity/slow_mode/?language=unity + /// https://getstream.io/chat/docs/unity/slow-mode/?language=unity#channel-slow-mode + /// + public async Task EnableDisableSlowMode() + { +// This feature is not yet available in the Unity SDK. +// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/slow-mode/?language=unity#channel-slow-mode /// - public async Task ThrottleAndSlowMode() + public async Task ReadChannelCooldown() { - //StreamTodo: IMPLEMENT Throttle and slow mode +// This feature is not yet available in the Unity SDK. +// Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues await Task.CompletedTask; } diff --git a/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs index 0dc7b442..72a949f2 100644 --- a/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/ClientAndUsersCodeSamples.cs @@ -14,7 +14,7 @@ namespace StreamChat.Samples internal sealed class ClientAndUsersCodeSamples { /// - /// https://getstream.io/chat/docs/unity/tokens_and_authentication/?language=unity#developer-tokens + /// https://getstream.io/chat/docs/unity/tokens-and-authentication/?language=unity#developer-tokens /// public async Task DeveloperTokens() { @@ -27,11 +27,11 @@ public async Task DeveloperTokens() var client = StreamChatClient.CreateDefaultClient(); // Connect user - var localUserData = await client.ConnectUserAsync("API_KEY", userId, userToken); + var localUserData = await client.ConnectUserAsync(credentials); } /// - /// https://getstream.io/chat/docs/unity/init_and_users/?language=unity + /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity /// public void InitClient() { @@ -39,36 +39,35 @@ public void InitClient() } /// - /// https://getstream.io/chat/docs/unity/init_and_users/?language=unreal#connecting-the-user + /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity#connecting-users /// public async Task ConnectUser() { var client = StreamChatClient.CreateDefaultClient(); +// 1. Static JWT token - prototyping/tests only. In production, issue tokens +// from your backend. var localUserData = await client.ConnectUserAsync("api_key", "chat_user", "chat_user_token"); -// After await is complete the user is connected +// Await returns once connected; you can also subscribe to client.Connected. + client.Connected += connectedUser => { /* User is connected */ }; +// 2. Production: implement ITokenProvider. The SDK calls GetTokenAsync on +// initial connect, reconnect, and expiration, so the WebSocket stays +// connected across token refreshes automatically. + var tokenProvider = new YourTokenProvider(); + await client.ConnectUserAsync("api_key", "chat_user", tokenProvider); } - - /// - /// https://getstream.io/chat/docs/unity/init_and_users/?language=unreal#connecting-the-user - /// - public async Task ConnectUser2() - { - var client = StreamChatClient.CreateDefaultClient(); - - await client.ConnectUserAsync("api_key", "chat_user", "chat_user_token"); -// After await is complete the user is connected -// Alternatively, you subscribe to the IStreamChatClient.Connected event - client.Connected += localUserData => - { - // User is connected - }; +// Your backend MUST authenticate the caller before issuing a Stream token - +// never expose an endpoint that returns a token for any userId. + public class YourTokenProvider : ITokenProvider + { + public Task GetTokenAsync(string userId) + => Task.FromResult("token-fetched-from-your-backend"); } /// - /// https://getstream.io/chat/docs/unity/init_and_users/?language=unity#websocket-connections + /// https://getstream.io/chat/docs/unity/init-and-users/?language=unity#disconnecting-users /// public async Task DisconnectUser() { @@ -76,29 +75,10 @@ public async Task DisconnectUser() await client.DisconnectUserAsync(); } - // Managing users https://getstream.io/chat/docs/unity/update_users/?language=unity - - /// - /// https://getstream.io/chat/docs/unity/update_users/?language=unity#delete-a-user - /// - public void DeleteUser() - { - //StreamTODO: Implement user delete - } - - /// - /// https://getstream.io/chat/docs/unity/logout/?language=unity - /// - public async Task LogoutUser() - { - await Client.DisconnectUserAsync(); - } - - #region Managing Users /// - /// https://getstream.io/chat/docs/unity/update_users/?language=unity#server-side-user-updates-(batch) + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#creating-and-updating-users-server-side /// public async Task UserUpdates() { @@ -123,6 +103,9 @@ public async Task UserUpdates() var users = await Client.UpsertUsersAsync(new[] { createOrUpdateUser }); } + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#creating-and-updating-users-server-side + /// public async Task UserUpdatesMultiple() { var usersToCreateOrUpdate = new[] @@ -153,26 +136,87 @@ public async Task UserUpdatesMultiple() var users = await Client.UpsertUsersAsync(usersToCreateOrUpdate); } + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#server-side-partial-updates + /// + public void PartialUpdateUser() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#unique-usernames + /// + public void UniqueUsernames() + { +// This can be set in https://dashboard.getstream.io/ -> Open your application -> Overview -> Authentication + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deactivate-a-user + /// + public void DeactivateUser() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deactivate-many-users + /// + public void DeactivateManyUsers() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#reactivate-a-user + /// + public void ReactivateUser() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#deleting-many-users + /// + public void DeleteUsers() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + + /// + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#restoring-deleted-users + /// + public void RestoreUsers() + { +// This is a server-side only feature, choose any of our server-side SDKs to use it + } + #endregion #region Querying Users /// - /// https://getstream.io/chat/docs/unity/query_users/?language=unity + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-users /// public async Task QueryUsers() { var filters = new IFieldFilterRule[] { - UserFilter.Id.In("user-1", "user-2", "user-3") + UserFilter.Id.In("john", "jack", "jessie") }; + + var sort = UsersSort.OrderByDescending(UserSortField.LastActive); + var limit = 10; + var offset = 0; + // Returns collection of IStreamUser - var users = await Client.QueryUsersAsync(filters); + var users = await Client.QueryUsersAsync(filters, sort, offset, limit); } - /// - /// https://getstream.io/chat/docs/unity/query_users/?language=unity - /// + // Code-only reference: the docs page does not host a separate offset/limit + // pagination snippet for QueryUsers (the limit + offset arguments are + // demonstrated inside the main "Querying Users" tab). public async Task QueryUsersPagination() { var lastWeek = DateTime.Now.AddDays(-7); @@ -186,41 +230,50 @@ public async Task QueryUsersPagination() var limit = 30; // How many records per page var offset = 0; // How many records to skip e.g. offset = 30 -> page 2, offset = 60 -> page 3, etc. - + // Returns collection of IStreamUser var users = await Client.QueryUsersAsync(filters, sort, offset, limit); } /// - /// https://getstream.io/chat/docs/unity/query_users/?language=unity#1.-by-name + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-with-autocomplete /// public async Task QueryUsersUsingAutocompleteByName() { var filters = new IFieldFilterRule[] { - UserFilter.Name.Autocomplete("Ro") + UserFilter.Name.Autocomplete("Ro") }; // Returns collection of IStreamUser var users = await Client.QueryUsersAsync(filters); } - /// - /// https://getstream.io/chat/docs/unity/query_users/?language=unity#2.-by-id - /// + // Code-only reference: the docs page only hosts a single autocomplete + // snippet (under "Querying with Autocomplete") which is mirrored by + // QueryUsersUsingAutocompleteByName above. public async Task QueryUsersUsingAutocompleteById() { var filters = new IFieldFilterRule[] { - // Return all users with Id starting with `Ro` like: Roxy, Roxanne, Rover - UserFilter.Name.Autocomplete("Ro") + // Return all users whose Id starts with `Ro` (e.g. Roxy, Roxanne, Rover) + UserFilter.Id.Autocomplete("Ro") }; // Returns collection of IStreamUser var users = await Client.QueryUsersAsync(filters); } /// - /// https://getstream.io/chat/docs/unity/query_users/?language=unity + /// https://getstream.io/chat/docs/unity/update-users/?language=unity#querying-inactive-users /// + public void QueryInactiveUsers() + { +// The $exists operator on last_active is not yet exposed in the Unity SDK. +// Use the UserFilter.LastActive comparison operators (e.g. LessThan / GreaterThanOrEquals) +// to filter users by their last-active timestamp. + } + + // Code-only reference: QueryBannedUsersAsync targets a different endpoint + // that is not documented on the Managing Users page. public async Task QueryBannedUsers() { // Returns collection of StreamUserBanInfo diff --git a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs index 18b7792f..bc03861f 100644 --- a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using StreamChat.Core; using StreamChat.Core.Models; @@ -10,18 +12,35 @@ namespace StreamChat.Samples { internal class EventsSamples { - public async Task QueryChannelsEvents() + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#listening-for-events + /// + public async Task ListeningForEvents() { - // Get a single channel + // 1. Client-level events + // Fire for the local user, regardless of which channels/messages/etc. are loaded. + Client.Connected += OnConnected; + Client.Disconnected += OnDisconnected; + Client.ConnectionStateChanged += OnConnectionStateChanged; + Client.AddedToChannelAsMember += OnAddedToChannelAsMember; + Client.RemovedFromChannelAsMember += OnRemovedFromChannelAsMember; + Client.ChannelDeleted += OnChannelDeleted; + Client.ChannelInviteReceived += OnChannelInviteReceived; + Client.ChannelInviteAccepted += OnChannelInviteAccepted; + Client.ChannelInviteRejected += OnChannelInviteRejected; + Client.ThreadTracked += OnThreadTracked; + Client.ThreadUntracked += OnThreadUntracked; + + // 2. Get a channel (or many) you want to listen on var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); - // Or multiple with optional filters - var channels = await Client.QueryChannelsAsync(new List() + var channels = await Client.QueryChannelsAsync(new List { ChannelFilter.Members.In(Client.LocalUserData.User) }); - // Subscribe to events + // 3. Channel-level events + // Fire only for channels you are watching (loaded via GetOrCreate* / QueryChannelsAsync). channel.MessageReceived += OnMessageReceived; channel.MessageUpdated += OnMessageUpdated; channel.MessageDeleted += OnMessageDeleted; @@ -33,19 +52,104 @@ public async Task QueryChannelsEvents() channel.MemberAdded += OnMemberAdded; channel.MemberRemoved += OnMemberRemoved; channel.MemberUpdated += OnMemberUpdated; - channel.MembersChanged += OnMembersChanged; + channel.VisibilityChanged += OnVisibilityChanged; channel.MuteChanged += OnMuteChanged; channel.Truncated += OnTruncated; channel.Updated += OnUpdated; + channel.WatcherAdded += OnWatcherAdded; channel.WatcherRemoved += OnWatcherRemoved; + channel.UserStartedTyping += OnUserStartedTyping; channel.UserStoppedTyping += OnUserStoppedTyping; channel.TypingUsersChanged += OnTypingUsersChanged; + + // 4. Per-message events + // Reaction events fire on the specific IStreamMessage instance. + var message = channel.Messages.First(); + message.ReactionAdded += OnReactionAdded; + message.ReactionUpdated += OnReactionUpdated; + message.ReactionRemoved += OnReactionRemoved; + + // 5. Per-thread events + // Load a thread via GetThreadAsync / QueryThreadsAsync (or via a channel that has tracked threads). + var thread = await Client.GetThreadAsync(message.Id); + thread.Updated += OnThreadUpdated; + thread.ReplyReceived += OnThreadReplyReceived; + thread.ReadStateChanged += OnThreadReadStateChanged; + + // 6. Per-user events + // Any IStreamUser instance (e.g. from channel.Members) exposes presence updates. + var user = channel.Members.First().User; + user.PresenceChanged += OnUserPresenceChanged; + + // 7. Per-poll events + // Load a poll via Client.Polls.GetPollAsync, or take it from a message that has one attached. + var poll = await Client.Polls.GetPollAsync("poll-id"); + poll.Closed += OnPollClosed; + poll.Updated += OnPollUpdated; + poll.VoteCasted += OnPollVoteCasted; + poll.VoteChanged += OnPollVoteChanged; + poll.VoteRemoved += OnPollVoteRemoved; + } + + // ---- Client-level handlers ---- + + private void OnConnected(IStreamLocalUserData localUserData) + { + } + + private void OnDisconnected() + { + } + + private void OnConnectionStateChanged(ConnectionState previous, ConnectionState current) + { + } + + private void OnAddedToChannelAsMember(IStreamChannel channel, IStreamChannelMember member) + { + // Fires for channels the local user was just added to and that are not yet watched locally. + // For watched channels, use channel.MemberAdded instead. + } + + private void OnRemovedFromChannelAsMember(IStreamChannel channel, IStreamChannelMember member) + { + // Fires for channels the local user was just removed from and that are not yet watched locally. + // For watched channels, use channel.MemberRemoved instead. + } + + private void OnChannelDeleted(string channelCid, string channelId, ChannelType channelType) + { + } + + private void OnChannelInviteReceived(IStreamChannel channel, IStreamUser invitee) + { + } + + private void OnChannelInviteAccepted(IStreamChannel channel, IStreamUser invitee) + { + } + + private void OnChannelInviteRejected(IStreamChannel channel, IStreamUser invitee) + { + } + + private void OnThreadTracked(IStreamThread thread) + { + // Fires when an IStreamThread becomes available locally (after GetThreadAsync, QueryThreadsAsync, + // or when watching a channel that contains threads). Use this to bind per-thread UI. } + private void OnThreadUntracked(IStreamThread thread) + { + // Fires when a tracked thread is no longer available (e.g. the parent message was hard-deleted). + } + + // ---- Channel-level handlers ---- + private void OnMessageReceived(IStreamChannel channel, IStreamMessage message) { } @@ -54,10 +158,11 @@ private void OnMessageUpdated(IStreamChannel channel, IStreamMessage message) { } - private void OnMessageDeleted(IStreamChannel channel, IStreamMessage message, bool isharddelete) + private void OnMessageDeleted(IStreamChannel channel, IStreamMessage message, bool isHardDelete) { } + // Reused for both channel.Reaction* and message.Reaction* (same delegate signature). private void OnReactionAdded(IStreamChannel channel, IStreamMessage message, StreamReaction reaction) { } @@ -86,7 +191,7 @@ private void OnMembersChanged(IStreamChannel channel, IStreamChannelMember membe { } - private void OnVisibilityChanged(IStreamChannel channel, bool isVisible) + private void OnVisibilityChanged(IStreamChannel channel, bool isHidden) { } @@ -122,43 +227,88 @@ private void OnTypingUsersChanged(IStreamChannel channel) { } - public void SubscribeToClientEvents() + // ---- Thread-level handlers ---- + + private void OnThreadUpdated(IStreamThread thread) { - Client.AddedToChannelAsMember += OnAddedToChannelAsMember; - Client.RemovedFromChannelAsMember += OnRemovedFromChannel; } - private void OnAddedToChannelAsMember(IStreamChannel channel, IStreamChannelMember member) + private void OnThreadReplyReceived(IStreamThread thread, IStreamMessage reply) { - // channel - new channel to which local user was just added - // member - object containing channel membership information } - private void OnRemovedFromChannel(IStreamChannel channel, IStreamChannelMember member) + private void OnThreadReadStateChanged(IStreamThread thread) { - // channel - channel from which local user was removed - // member - object containing channel membership information } - public void SubscribeToConnectionEvents() + // ---- User-level handlers ---- + + private void OnUserPresenceChanged(IStreamUser user, bool isOnline, DateTimeOffset? lastActive) { - Client.Connected += OnConnected; - Client.Disconnected += OnDisconnected; - Client.ConnectionStateChanged += OnConnectionStateChanged; } - private void OnConnected(IStreamLocalUserData localUserData) + // ---- Poll-level handlers ---- + + private void OnPollClosed(IStreamPoll poll) { } - private void OnDisconnected() + private void OnPollUpdated(IStreamPoll poll) { } - private void OnConnectionStateChanged(ConnectionState previous, ConnectionState current) + private void OnPollVoteCasted(IStreamPoll poll, StreamPollVote vote) + { + } + + private void OnPollVoteChanged(IStreamPoll poll, StreamPollVote vote) + { + } + + private void OnPollVoteRemoved(IStreamPoll poll, StreamPollVote vote) { } + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#listening-for-events + /// (the "You can also listen to all events at once" Unity tab) + /// + public void ListenToAllEventsAtOnce() + { + // Not supported in the Unity SDK + } + + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#event-types + /// + public async Task ListenForUserPresenceEvents() + { + // Get a channel + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + + // Each user object exposes the PresenceChanged event + foreach (var member in channel.Members) + { + member.User.PresenceChanged += (userObj, isOnline, isActive) => + { + // Handle presence change + }; + } + } + + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#connection-events + /// + public void SubscribeToConnectionEvents() + { + Client.Connected += OnConnected; + Client.Disconnected += OnDisconnected; + Client.ConnectionStateChanged += OnConnectionStateChanged; + } + + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#stop-listening-for-events + /// public void Unsubscribe() { Client.Connected -= OnConnected; @@ -166,6 +316,22 @@ public void Unsubscribe() Client.ConnectionStateChanged -= OnConnectionStateChanged; } + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#to-a-channel + /// + public void SendCustomEventToChannel() + { + // Not yet supported in the Unity SDK + } + + /// + /// https://getstream.io/chat/docs/unity/event-object/?language=unity#to-a-user + /// + public void SendCustomEventToUser() + { + // Not yet supported in the Unity SDK + } + private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); } -} \ No newline at end of file +} diff --git a/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs index 81a0e41f..9c3bd701 100644 --- a/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/MessagesCodeSamples.cs @@ -3,7 +3,10 @@ using System.IO; using System.Threading.Tasks; using StreamChat.Core; -using StreamChat.Core.LowLevelClient.Requests; +using StreamChat.Core.QueryBuilders.Filters; +using StreamChat.Core.QueryBuilders.Filters.Channels; +using StreamChat.Core.QueryBuilders.Filters.Messages; +using StreamChat.Core.QueryBuilders.Sort; using StreamChat.Core.Requests; using StreamChat.Core.StatefulModels; using UnityEngine; @@ -13,33 +16,73 @@ namespace StreamChat.Samples internal sealed class MessagesCodeSamples { /// - /// https://getstream.io/chat/docs/unity/send_message/?language=unity + /// https://getstream.io/chat/docs/unity/send-message/?language=unity#sending-a-message /// public async Task Overview() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); - var message = await channel.SendNewMessageAsync("Hello world!"); + var message = await channel.SendNewMessageAsync("Hello, world!"); } /// - /// https://getstream.io/chat/docs/unity/send_message/?language=unity#complex-example + /// https://getstream.io/chat/docs/unity/send-message/?language=unity#sending-messages-with-attachments /// + public async Task SendingMessagesWithAttachments() + { + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); + + IStreamUser josh = null; + + var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest + { + Text = "@Josh Check out this image!", + + // All fields below are optional + Attachments = new List + { + new StreamAttachmentRequest + { + Type = "image", + AssetUrl = "https://bit.ly/2K74TaG", + ThumbUrl = "https://bit.ly/2Uumxti", + } + }, + MentionedUsers = new List { josh }, + Pinned = true, + PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7), + CustomData = new StreamCustomDataRequest + { + { "priority", "high" } + } + }); + } + + // Code-only reference (kitchen-sink demo of every send-message option the + // stateful Unity SDK exposes — threading, quoting, pinning, mentions, + // and custom data in a single call). The published docs do not host an + // equivalent "complex example" group on /send-message/ since the page + // restructure on 2025-12-11 (commit 3ccdc786e), so per the orphan rule + // there is no `///` URL to point at — the page now splits these + // features across /threads/ (parent_id, show_in_channel), + // /pinned-messages/ (pinned, pin_expires) and the section-specific + // tabs above. Kept as a single grep-friendly reference for Unity + // readers who want to see every option in one spot. public async Task ComplexExample() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); IStreamUser someUser = null; -// Send simple message with text only + // Send a quoted message var message3 = await channel.SendNewMessageAsync("Hello"); -// Send simple message with text only + // Send a parent message we can reply to in a thread var message2 = await channel.SendNewMessageAsync("Let's start a thread!"); var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest { MentionedUsers = new List { someUser }, // Mention a user - ParentId = message2.Id, // Write in thread + ParentId = message2.Id, // Write in a thread PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7), // Pin for 7 days Pinned = true, QuotedMessage = message3, @@ -61,20 +104,19 @@ public async Task GetMessageById() } /// - /// https://getstream.io/chat/docs/unity/send_message/?language=unity#update-a-message + /// https://getstream.io/chat/docs/unity/send-message/?language=unity#updating-a-message /// public async Task UpdateAMessage() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); - var message = await channel.SendNewMessageAsync("Hello world!"); + var message = await channel.SendNewMessageAsync("Hello, world!"); -// Edit message text and some custom data await message.UpdateAsync(new StreamUpdateMessageRequest { - Text = "Hi everyone!", + Text = "Updated message text", CustomData = new StreamCustomDataRequest { - { "tags", new[] { "Funny", "Unique" } } + { "tags", new[] { "edited" } } } }); } @@ -88,17 +130,17 @@ public async Task PartialUpdate() } /// - /// https://getstream.io/chat/docs/unity/send_message/?language=unity#delete-a-message + /// https://getstream.io/chat/docs/unity/send-message/?language=unity#deleting-a-message /// public async Task DeleteAMessage() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); - var message = await channel.SendNewMessageAsync("Hello world!"); + var message = await channel.SendNewMessageAsync("Hello, world!"); -// Soft delete + // Soft delete await message.SoftDeleteAsync(); -// Hard delete + // Hard delete await message.HardDeleteAsync(); } @@ -111,25 +153,35 @@ public async Task OpenGraphScrapper() } /// - /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#how-to-upload-a-file-or-image + /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#uploading-files-to-a-channel /// public async Task UploadFileOrImage() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); -// Get file byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc. - var sampleFile = File.ReadAllBytes("path/to/file"); - var fileUploadResponse = await channel.UploadFileAsync(sampleFile, "my-file-name"); - var fileWebUrl = fileUploadResponse.FileUrl; + var imageBytes = File.ReadAllBytes("path/to/image.jpg"); + var imageUploadResponse = await channel.UploadImageAsync(imageBytes, "image.jpg"); + var imageUrl = imageUploadResponse.FileUrl; -// Get image byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc. - var sampleImage = File.ReadAllBytes("path/to/file"); - var imageUploadResponse = await channel.UploadImageAsync(sampleFile, "my-image-name"); - var imageWebUrl = imageUploadResponse.FileUrl; + var fileBytes = File.ReadAllBytes("path/to/document.pdf"); + var fileUploadResponse = await channel.UploadFileAsync(fileBytes, "document.pdf"); + var fileUrl = fileUploadResponse.FileUrl; } /// - /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#deleting-files-and-images + /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#uploading-standalone-files + /// + public Task UploadStandaloneFiles() + { + // Uploading standalone files via a client-level endpoint is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + // As a workaround, upload via channel.UploadImageAsync / channel.UploadFileAsync + // and reuse the returned URL when upserting a user with Client.UpsertUsersAsync. + return Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#deleting-files /// public async Task DeleteFileOrImage() { @@ -138,13 +190,13 @@ public async Task DeleteFileOrImage() } /// - /// https://getstream.io/chat/docs/unity/file_uploads/?language=unity#using-your-own-cdn + /// https://getstream.io/chat/docs/unity/file-uploads/?language=unity#using-your-own-cdn /// public async Task UsingYourOwnCdn() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); -//Implement your own CDN upload and obtain the file URL + // Upload to your CDN and obtain the file URL var fileUrl = "file-url-to-your-cdn"; await channel.SendNewMessageAsync(new StreamSendMessageRequest @@ -158,33 +210,28 @@ await channel.SendNewMessageAsync(new StreamSendMessageRequest } } }); - - await Task.CompletedTask; } /// - /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity + /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#sending-a-reaction /// public async Task ReactionOverview() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); var message = await channel.SendNewMessageAsync("Hello world!"); -// Send simple reaction with a score of 1 + // Send a reaction with default score of 1 await message.SendReactionAsync("like"); -// Send reaction with a custom score value - await message.SendReactionAsync("clap", 10); - -// Send reaction with a custom score value + // Send a reaction with custom score await message.SendReactionAsync("clap", 10); -// Send reaction and replace all previous reactions (if any) from this user + // Replace all previous reactions from this user await message.SendReactionAsync("love", enforceUnique: true); } /// - /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#removing-a-reaction + /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#removing-a-reaction /// public async Task RemoveReaction() { @@ -195,25 +242,61 @@ public async Task RemoveReaction() } /// - /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#paginating-reactions + /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#paginating-reactions /// public async Task PaginateReactions() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); var message = await channel.SendNewMessageAsync("Hello world!"); - //StreamTodo: IMPLEMENT reactions paginating + // Paginating reactions via API is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + // Each message exposes a snapshot of the reaction summary instead: + + // The 10 most recent reactions on the message + foreach (var reaction in message.LatestReactions) + { + Debug.Log($"{reaction.Type} from {reaction.User?.Id}"); + } + + // The 10 most recent reactions left by the local user + foreach (var reaction in message.OwnReactions) + { + Debug.Log($"My reaction: {reaction.Type}"); + } + + // Number of reactions per type, e.g. {"love": 3, "fire": 2} + foreach (var entry in message.ReactionCounts) + { + Debug.Log($"{entry.Key}: {entry.Value} reactions"); + } + + // Sum of reaction scores per type (matches counts unless cumulative reactions are used) + foreach (var entry in message.ReactionScores) + { + Debug.Log($"{entry.Key}: total score {entry.Value}"); + } } /// - /// https://getstream.io/chat/docs/unity/send_reaction/?language=unity#cumulative-(clap)-reactions + /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#querying-reactions + /// + public async Task QueryReactions() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/send-reaction/?language=unity#cumulative-reactions /// public async Task CumulativeReactions() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); var message = await channel.SendNewMessageAsync("Hello world!"); - await message.SendReactionAsync("clap", score: 3); + await message.SendReactionAsync("clap", score: 5); } /// @@ -284,10 +367,90 @@ public async Task QuoteMessage() } /// - /// https://getstream.io/chat/docs/unity/reminders/?language=unity + /// https://getstream.io/chat/docs/unity/unread-reminders/?language=unity#enabling-unread-reminders + /// + public async Task EnableUnreadReminders() + { + // Enable in Dashboard: Open your application -> Channel Types -> Pick Channel Type -> Enable "Message Reminders" + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#enabling-reminders /// - public async Task Reminders() + public async Task EnableMessageReminders() { + // This is a server-side feature, choose any of our server-side SDKs (or the Stream Dashboard) to enable it + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#creating-a-message-reminder + /// + public async Task CreateMessageReminder() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#updating-a-message-reminder + /// + public async Task UpdateMessageReminder() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#deleting-a-message-reminder + /// + public async Task DeleteMessageReminder() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#querying-message-reminders + /// + public async Task QueryMessageReminders() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#filtering-reminders + /// + public async Task FilterMessageReminders() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#pagination + /// + public async Task PaginateMessageReminders() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + await Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/message-reminders/?language=unity#events + /// + public async Task MessageReminderEvents() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues await Task.CompletedTask; } @@ -311,32 +474,43 @@ public async Task SilentMessages() /// public async Task Search() { -// Access to low-level client is left for backward compatibility. Soon simplified syntax for searching will be implemented - var searchResponse = await Client.LowLevelClient.MessageApi.SearchMessagesAsync(new SearchRequest + // Search for messages containing text + var results = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest { - //Filter is required for search - FilterConditions = new Dictionary + // Channel filter is required - here, channels the local user is a member of + ChannelFilter = new IFieldFilterRule[] { - { - //Get channels that local user is a member of - "members", new Dictionary - { - { "$in", new[] { "John" } } - } - } + ChannelFilter.Members.In("john"), }, - - //search phrase - Query = "supercalifragilisticexpialidocious" + Query = "supercalifragilisticexpialidocious", + Limit = 10, }); - foreach (var searchResult in searchResponse.Results) + foreach (var hit in results.Results) { - Debug.Log(searchResult.Message.Id); //Message ID - Debug.Log(searchResult.Message.Text); //Message text - Debug.Log(searchResult.Message.User); //Message author info - Debug.Log(searchResult.Message.Channel); //Channel info + Debug.Log(hit.Message.Id); // Stateful IStreamMessage + Debug.Log(hit.Message.Text); + Debug.Log(hit.Message.User); + Debug.Log(hit.Channel.Cid); // Stateful IStreamChannel (auto-watched by default) } + + // Search with message filters - mutually exclusive with Query + var filtered = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest + { + ChannelFilter = new IFieldFilterRule[] + { + ChannelFilter.Members.In("john"), + }, + MessageFilter = new IFieldFilterRule[] + { + MessageFilter.Text.Autocomplete("super"), + MessageFilter.AttachmentType.In("image", "video"), + }, + Limit = 10, + // Set to false for one-off search bars where you don't want every result + // channel to start receiving realtime updates. + WatchResultChannels = true, + }); } /// @@ -344,64 +518,125 @@ public async Task Search() /// public async Task SearchPagination() { - await Task.CompletedTask; + var channelFilters = new IFieldFilterRule[] + { + ChannelFilter.Cid.EqualsTo("messaging:my-channel"), + }; + var messageFilters = new IFieldFilterRule[] + { + MessageFilter.Text.Autocomplete("supercali"), + }; + + // First page with custom sorting + var page1 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest + { + ChannelFilter = channelFilters, + MessageFilter = messageFilters, + Sort = MessagesSort + .OrderByDescending(MessageSortFieldName.Relevance) + .ThenByAscending(MessageSortFieldName.UpdatedAt), + Limit = 10, + }); + + // Next page using the cursor returned by the previous response + var page2 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest + { + ChannelFilter = channelFilters, + MessageFilter = messageFilters, + Limit = 10, + Next = page1.Next, + }); + + // Previous page + var page1Again = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest + { + ChannelFilter = channelFilters, + MessageFilter = messageFilters, + Limit = 10, + Next = page2.Previous, + }); } /// - /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#pin-and-unpin-a-message + /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#pinning-and-unpinning-messages /// public async Task PinAndUnpinMessage() { - IStreamMessage message = null; + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); + var message = await channel.SendNewMessageAsync("Important announcement"); -// Pin until unpinned + // Pin until unpinned await message.PinAsync(); -// Pin for 7 days - await message.PinAsync(new DateTime().AddDays(7)); + // Pin for 7 days + await message.PinAsync(DateTime.UtcNow.AddDays(7)); -// Unpin previously pinned message + // Unpin previously pinned message await message.UnpinAsync(); } /// - /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#retrieve-pinned-messages + /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#retrieving-pinned-messages /// public async Task RetrievePinnedMessages() { - await Task.CompletedTask; + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); + + // channel.PinnedMessages exposes the 10 most recent pinned messages loaded with the channel state. + foreach (var pinnedMessage in channel.PinnedMessages) + { + Debug.Log($"{pinnedMessage.Id}: {pinnedMessage.Text}"); + } } /// - /// https://getstream.io/chat/docs/unity/pinned_messages/?language=unity#paginate-over-all-pinned-messages + /// https://getstream.io/chat/docs/unity/pinned-messages/?language=unity#paginating-pinned-messages /// public async Task PaginatePinnedMessages() { + // Paginating pinned messages via the dedicated pinned-messages endpoint is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + // The channel.PinnedMessages snapshot loaded with the channel state (max 10) is the available alternative. await Task.CompletedTask; } /// /// https://getstream.io/chat/docs/unity/translation/?language=unity#message-translation-endpoint + /// On-demand message translation is not yet exposed by the Unity SDK; the docs + /// surface the canonical "raise an issue" placeholder. Tracked in B5 of the docs + /// update plan. /// - public async Task MessageTranslation() + public Task MessageTranslation() { - await Task.CompletedTask; + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; } /// /// https://getstream.io/chat/docs/unity/translation/?language=unity#enabling-automatic-translation + /// Enabling automatic translation (per-channel or per-app) is server-side / not + /// yet exposed by the Unity stateful client; the docs surface the canonical + /// "raise an issue" placeholder. Tracked in B5 of the docs update plan. /// - public async Task EnableAutomaticTranslation() + public Task EnableAutomaticTranslation() { - await Task.CompletedTask; + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; } /// /// https://getstream.io/chat/docs/unity/translation/?language=unity#set-user-language + /// Setting the user language at connect time is not yet exposed by the Unity + /// stateful client; the docs surface the canonical "raise an issue" placeholder. + /// Tracked in B5 of the docs update plan. /// - public async Task SetUserLanguage() + public Task SetUserLanguage() { - await Task.CompletedTask; + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; } private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); diff --git a/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs new file mode 100644 index 00000000..02df0c8a --- /dev/null +++ b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs @@ -0,0 +1,324 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using StreamChat.Core; +using StreamChat.Core.QueryBuilders.Filters; +using StreamChat.Core.QueryBuilders.Filters.Polls; +using StreamChat.Core.QueryBuilders.Sort; +using StreamChat.Core.Requests; +using StreamChat.Core.StatefulModels; + +namespace StreamChat.Samples +{ + internal sealed class PollsCodeSamples + { + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#polls-at-a-quick-glance + /// + public async Task QuickGlance() + { + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + + // Create a poll and send it in a message + var poll = await Client.Polls.CreatePollAsync(new StreamCreatePollRequest + { + Name = "Where should we host our next event?", + Options = new List + { + new StreamPollOptionRequest { Text = "Amsterdam" }, + new StreamPollOptionRequest { Text = "Boulder" }, + }, + }); + var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest + { + Text = "Vote now!", + PollId = poll.Id, + }); + + // Vote on a poll + var optionId = poll.Options[0].Id; + await poll.CastVoteAsync(message.Id, optionId); + + // Retrieve poll results + var refreshed = await Client.Polls.GetPollAsync(poll.Id); + var voteCountsByOption = refreshed.VoteCountsByOption; // { 'option-id': 5 } + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#creating-a-poll-and-sending-it-as-part-of-a-message + /// + public async Task CreatePollWithMessage() + { + // Create a poll with options + var createPollRequest = new StreamCreatePollRequest + { + Name = "Where should we host our next company event?", + Options = new List + { + new StreamPollOptionRequest { Text = "Amsterdam, The Netherlands" }, + new StreamPollOptionRequest { Text = "Boulder, CO" } + } + }; + + var poll = await Client.Polls.CreatePollAsync(createPollRequest); + + // Get or create a channel + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + + // Send a message with the poll + var messageRequest = new StreamSendMessageRequest + { + Text = "We want to know your opinion!", + PollId = poll.Id + }; + + var message = await channel.SendNewMessageAsync(messageRequest); + + // message.PollId contains the poll ID + // Others users can use poll ID to fetch the poll object + var pollData = await Client.Polls.GetPollAsync(message.PollId); + + // The poll object contains data, events, and operations: + // - Data: pollData.Name, pollData.Options, pollData.VoteCount, pollData.IsClosed, etc. + // - Events: pollData.Updated, pollData.Closed, pollData.VoteCasted, etc. + // - Operations: pollData.CastVoteAsync(), pollData.CloseAsync(), pollData.UpdateAsync(), etc. + // Explore the IStreamPoll interface for complete reference + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#poll-options + /// Custom properties on a poll and on individual options. + /// + public async Task CreatePollWithCustomData() + { + var createPollRequest = new StreamCreatePollRequest + { + Name = "Where should we host our next company event?", + Options = new List + { + new StreamPollOptionRequest + { + Text = "Amsterdam, The Netherlands", + Custom = new Dictionary + { + { "venue_capacity", 300 } + } + }, + new StreamPollOptionRequest + { + Text = "Boulder, CO", + Custom = new Dictionary + { + { "venue_capacity", 1000 } + } + } + }, + Custom = new Dictionary + { + { "category", "company-events" }, + { "priority", 5 } + } + }; + + var poll = await Client.Polls.CreatePollAsync(createPollRequest); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#send-vote-on-option + /// + public async Task CastVoteOnOption() + { + // Get the channel and message + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + + // Assume we have a message with a poll + var message = channel.Messages.First(m => !string.IsNullOrEmpty(m.PollId)); + var poll = await Client.Polls.GetPollAsync(message.PollId); + + // Get an option to vote on + var option = poll.Options[0]; + + // Cast a vote on the option + await poll.CastVoteAsync(message.Id, option.Id); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#send-an-answer-(if-answers-are-configured-to-be-allowed) + /// Poll answers are not yet exposed by the Unity SDK; the docs show the + /// "raise an issue" placeholder. Tracked in F11 of the docs update plan. + /// + public Task CastAnswer() + { + // Poll answers are not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#removing-a-vote + /// + public async Task RemoveVote() + { + // Get the channel and message + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + + // Assume we have a message with a poll + var message = channel.Messages.First(m => !string.IsNullOrEmpty(m.PollId)); + var poll = await Client.Polls.GetPollAsync(message.PollId); + + // Get a vote to remove (e.g., from own votes) + var vote = poll.OwnVotes[0]; + + // Remove the vote + await poll.RemoveVoteAsync(message.Id, vote.Id); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#closing-a-poll + /// + public async Task ClosePoll() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Close the poll to prevent further votes + await poll.CloseAsync(); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#retrieving-a-poll + /// + public async Task RetrievePoll() + { + // Retrieve a poll by ID + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Access poll properties + var name = poll.Name; + var description = poll.Description; + var options = poll.Options; + var voteCount = poll.VoteCount; + var isClosed = poll.IsClosed; + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#full-update + /// + public async Task UpdatePollFull() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Update the poll (full update) + var updateRequest = new StreamUpdatePollRequest + { + Name = "Where should we not go to?", + Description = "Updated description" + }; + + await poll.UpdateAsync(updateRequest); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#partial-update + /// + public async Task UpdatePollPartial() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Partial update - set specific fields and/or unset fields + await poll.UpdatePartialAsync( + setFields: new Dictionary + { + { "name", "Where should we not go to?" }, + { "max_votes_allowed", 3 } + }, + unsetFields: new List { "custom_property" }); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#deleting-a-poll + /// + public async Task DeletePoll() + { + // Delete a poll by ID through the Polls API + await Client.Polls.DeletePollAsync("poll-id"); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#add-poll-option + /// + public async Task AddPollOption() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Add a new option - takes a text string parameter + var newOption = await poll.AddOptionAsync("Another option"); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#update-poll-option + /// + public async Task UpdatePollOption() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Get an option to update + var option = poll.Options[0]; + + // Update the option - takes optionId and text parameters + var updatedOption = await poll.UpdateOptionAsync(option.Id, "Updated option"); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#delete-poll-option + /// + public async Task DeletePollOption() + { + // Get the poll reference + var poll = await Client.Polls.GetPollAsync("poll-id"); + + // Get an option to delete + var option = poll.Options[0]; + + // Delete the option + await poll.DeleteOptionAsync(option.Id); + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#querying-votes + /// Querying poll votes is not yet exposed by the Unity SDK. The docs + /// surface this placeholder; tracked in F11 of the docs update plan. + /// + public Task QueryPollVotes() + { + // Querying poll votes is not yet available in the Unity SDK. + // You can access latest votes through poll.LatestVotesByOption and poll.OwnVotes properties. + // Please let us know if you'd like full vote querying implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; + } + + /// + /// https://getstream.io/chat/docs/unity/polls-api/?language=unity#querying-polls + /// + public async Task QueryPolls() + { + // Retrieve all polls that are closed for voting, sorted by created_at + var queryRequest = new StreamQueryPollsRequest + { + Filter = new IFieldFilterRule[] + { + PollFilter.IsClosed.EqualsTo(true) + }, + Sort = PollSort.OrderByDescending(PollSortFieldName.CreatedAt) + }; + + var polls = await Client.Polls.QueryPollsAsync(queryRequest); + } + + private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); + } +} diff --git a/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta new file mode 100644 index 00000000..73aec564 --- /dev/null +++ b/Assets/Plugins/StreamChat/Samples/PollsCodeSamples.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7b1c3e8d4a6f4f7da1e0d2f8a9c5b1e2 +timeCreated: 1748966400 diff --git a/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs new file mode 100644 index 00000000..d05f8799 --- /dev/null +++ b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading.Tasks; +using StreamChat.Core; +using StreamChat.Core.Exceptions; +using StreamChat.Core.StatefulModels; +using UnityEngine; + +namespace StreamChat.Samples +{ + internal sealed class RateLimitsCodeSamples + { + /// + /// https://getstream.io/chat/docs/unity/rate-limits/?language=unity#catching-429s-in-backend-sdks + /// + public async Task CatchingApiErrors() + { + IStreamChannel channel = null; + + // Example 1 — single catch + switch on the numeric error code. + try + { + await channel.SendNewMessageAsync("Hello"); + } + catch (StreamApiException ex) + { + // ex.Code / ex.StatusCode / ex.ErrorMessage / ex.ExceptionFields / ex.MoreInfo + // are all available for inspection. See the API Error Codes docs page + // for the full list. + switch (ex.Code) + { + case StreamApiException.RateLimitErrorStreamCode: + // HTTP 429 — back off with exponential delay before retrying. + await Task.Delay(TimeSpan.FromSeconds(1)); + break; + case StreamApiException.CooldownErrorStreamCode: + // HTTP 403 — slow-mode cooldown. Gate the send UI for `channel.Cooldown` seconds. + break; + case StreamApiException.PermissionDeniedErrorStreamCode: + // HTTP 403 — user lacks permission. Hide / disable the control. + break; + default: + Debug.LogError($"Stream API error {ex.Code} (HTTP {ex.StatusCode}): {ex.ErrorMessage}"); + break; + } + } + + // Alternatively, use the `when` syntax combined with our dedicated + // error-checking extensions. + try + { + await channel.SendNewMessageAsync("Hello"); + } + catch (StreamApiException ex) when (ex.IsRateLimitExceededError()) + { + // HTTP 429 / Stream code 9 — back off before retrying. + await Task.Delay(TimeSpan.FromSeconds(1)); + } + + // Most common error cases have a dedicated extension method on StreamApiException. + // Pick the helper that matches your branch instead of comparing `ex.Code` by hand: + // IsRateLimitExceededError (429 / 9) — back off and retry + // IsCooldownError (403 / 60) — gate UI for `channel.Cooldown` seconds + // IsPermissionDeniedError (403 / 17) — hide the UI control + // IsNoAccessToChannelsError (403 / 70) — drop the channel locally + // IsAppSuspendedError (403 / 99) — show service-unavailable UI + // IsAuthenticationError (401 / 5) — send the user back to sign-in + // IsTokenExpiredError (401 / 40) — refresh the token + // IsTokenError (401 / 40-43) — any token-related failure + // IsDoesNotExistError (404 / 16) — refresh the local view + // IsInputError (400 / 4) — inspect `ex.ExceptionFields` + // IsMessageTooLongError (400 / 20) — show character-limit error + // IsMessageModerationFailedError (400 / 73) — show "filtered" UI + // IsPayloadTooBigError (413 / 22) — ask for a smaller file + // IsInternalSystemError (500 / -1) — retry, then surface a transient-failure UI + // + // The general guidance: wrap a call in try/catch where you want to react + // specifically (rate-limit back-off, cooldown UI, validation feedback, + // permission gating). For everything else, let the exception propagate to + // your global error handler — every SDK call already throws StreamApiException + // on server-rejected requests, so a single boundary catch is enough. + } + + private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); + } +} diff --git a/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta new file mode 100644 index 00000000..7cc70a53 --- /dev/null +++ b/Assets/Plugins/StreamChat/Samples/RateLimitsCodeSamples.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ff4fd277f0449ce87f3982fa0659cec +timeCreated: 1749567000 diff --git a/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs index 307a5018..15a720c2 100644 --- a/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/ThreadsCodeSamples.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using StreamChat.Core; @@ -6,144 +7,224 @@ using StreamChat.Core.QueryBuilders.Sort; using StreamChat.Core.Requests; using StreamChat.Core.StatefulModels; +using UnityEngine; namespace StreamChat.Samples { internal sealed class ThreadsCodeSamples { /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity + /// https://getstream.io/chat/docs/unity/threads/?language=unity#starting-a-thread /// public async Task SendReply() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); - var parentMessage = await channel.SendNewMessageAsync("Let's start a thread!"); + var parentMessage = await channel.SendNewMessageAsync("Starting a thread"); - // Reply in the thread (won't appear in main channel timeline) var reply = await channel.SendNewMessageAsync(new StreamSendMessageRequest { ParentId = parentMessage.Id, ShowInChannel = false, - Text = "Hello!", + Text = "This is a reply in a thread", }); } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#load-replies + /// https://getstream.io/chat/docs/unity/threads/?language=unity#paginating-thread-replies /// public async Task LoadReplies() { var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + var parentMessage = await channel.SendNewMessageAsync("Starting a thread"); - var parentMessage = await channel.SendNewMessageAsync("Let's start a thread!"); + // Get the latest 20 replies (oldest-first) + var replies = await parentMessage.LoadRepliesAsync(limit: 20); - // Load the most recent replies of a thread (oldest-first) - var firstPage = await parentMessage.LoadRepliesAsync(limit: 25); + // Get older replies (before message with id "42") + var olderReplies = await parentMessage.LoadRepliesAsync(limit: 20, idLessThan: "42"); + } + + /// + /// https://getstream.io/chat/docs/unity/threads/?language=unity#inline-replies + /// + public async Task InlineReply() + { + var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); + var originalMessage = await channel.SendNewMessageAsync("Original message"); - // Load older replies using id_lt pagination - if (firstPage.Count > 0) + var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest { - var olderPage = await parentMessage.LoadRepliesAsync(limit: 25, idLessThan: firstPage[0].Id); - } + QuotedMessage = originalMessage, + Text = "I agree with this point", + }); } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#get-thread + /// https://getstream.io/chat/docs/unity/threads/?language=unity#querying-threads /// - public async Task GetThread() + public async Task QueryThreads() { - // Fetch a thread by its parent message id - var thread = await Client.GetThreadAsync("parent-message-id", replyLimit: 25, participantLimit: 25); - - var participants = thread.ThreadParticipants; - var replies = thread.LatestReplies; - var replyCount = thread.ReplyCount; + var response = await Client.QueryThreadsAsync(new StreamQueryThreadsRequest + { + Watch = true, + Limit = 10, + }); - // Subscribe to thread updates - thread.Updated += changedThread => { /* handle change */ }; - thread.ReplyReceived += (changedThread, reply) => { /* handle new reply */ }; - thread.ReadStateChanged += changedThread => { /* unread state changed */ }; + foreach (var thread in response.Threads) + { + // Threads are cached, watched and kept in sync with realtime events + Debug.Log(thread.ParentMessage.Text); + Debug.Log(thread.LatestReplies); + Debug.Log(thread.ThreadParticipants); + Debug.Log(thread.Read); + } } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#query-threads + /// https://getstream.io/chat/docs/unity/threads/?language=unity#filtering-and-sorting /// - public async Task QueryThreads() + public async Task QueryThreadsWithFilterAndSort() { + var since = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero); + var request = new StreamQueryThreadsRequest { - Limit = 20, - ReplyLimit = 5, - ParticipantLimit = 5, - Watch = true, Filter = new IFieldFilterRule[] { - ThreadFilter.ChannelCid.EqualsTo("messaging:my-channel-id"), + ThreadFilter.CreatedByUserId.EqualsTo("user-1"), + ThreadFilter.UpdatedAt.GreaterThanOrEquals(since), }, - Sort = ThreadSort.OrderByDescending(ThreadSortFieldName.LastMessageAt), + Sort = ThreadSort.OrderByDescending(ThreadSortFieldName.CreatedAt), + Limit = 10, }; - var response = await Client.QueryThreadsAsync(request); + var page1 = await Client.QueryThreadsAsync(request); - foreach (var thread in response.Threads) + // Get next page using the cursor returned by the previous response + if (!string.IsNullOrEmpty(page1.Next)) { - // Use thread state. The thread is already cached and gets WS updates. - var title = thread.Title; - var replyCount = thread.ReplyCount; + request.Next = page1.Next; + var page2 = await Client.QueryThreadsAsync(request); } + } - // Pagination - if (!string.IsNullOrEmpty(response.Next)) - { - request.Next = response.Next; - var nextPage = await Client.QueryThreadsAsync(request); - } + /// + /// https://getstream.io/chat/docs/unity/threads/?language=unity#getting-a-thread-by-id + /// + public async Task GetThread() + { + // The returned IStreamThread is auto-watched (watch defaults to true) and stays in + // sync with realtime events via Updated / ReplyReceived / ReadStateChanged. + var thread = await Client.GetThreadAsync("parent-message-id", + replyLimit: 10, participantLimit: 25); + + var participants = thread.ThreadParticipants; + var replies = thread.LatestReplies; + var replyCount = thread.ReplyCount; + + thread.Updated += changedThread => { /* title or custom data changed */ }; + thread.ReplyReceived += (changedThread, reply) => { /* new reply arrived */ }; + thread.ReadStateChanged += changedThread => { /* unread state changed */ }; } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#partial-update + /// https://getstream.io/chat/docs/unity/threads/?language=unity#updating-thread-title-and-custom-data /// public async Task UpdateThread() { var thread = await Client.GetThreadAsync("parent-message-id"); + // Set title and custom fields; unset a previously set field await thread.UpdatePartialAsync( setFields: new Dictionary { - { "title", "Updated thread title" }, - { "my_custom_field", 42 }, + { "title", "Project Discussion" }, + { "priority", "high" }, }, - unsetFields: new[] { "obsolete_field" }); + unsetFields: new[] { "priority" }); } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#mark-as-read + /// https://getstream.io/chat/docs/unity/threads/?language=unity#total-unread-threads + /// + public async Task ObserveTotalUnreadThreads() + { + // Available immediately after connect on IStreamLocalUserData + var unreadThreads = Client.LocalUserData.UnreadThreads; + Debug.Log(unreadThreads); + + // The same total is also available on demand from the server + var unreadCounts = await Client.GetLatestUnreadCountsAsync(); + Debug.Log(unreadCounts.TotalUnreadThreadsCount); + } + + /// + /// https://getstream.io/chat/docs/unity/threads/?language=unity#marking-threads-as-read-or-unread /// public async Task MarkThreadReadAndUnread() { var thread = await Client.GetThreadAsync("parent-message-id"); - // Mark this thread as read + // Mark this thread as read for the local user await thread.MarkReadAsync(); // Mark this thread as unread starting from the parent message await thread.MarkUnreadAsync(); - // From a message reference (the parent) + // Equivalent helpers from the parent message of the thread IStreamMessage parentMessage = thread.ParentMessage; await parentMessage.MarkThreadAsReadAsync(); await parentMessage.MarkThreadAsUnreadAsync(); + + // Or by parent message id when you already have the channel + await thread.Channel.MarkThreadAsReadAsync(thread.ParentMessageId); + await thread.Channel.MarkThreadAsUnreadAsync(thread.ParentMessageId); + } + + /// + /// https://getstream.io/chat/docs/unity/threads/?language=unity#unread-count-per-thread + /// + public async Task UnreadCountPerThread() + { + var unreadCounts = await Client.GetLatestUnreadCountsAsync(); + + Debug.Log(unreadCounts.TotalUnreadThreadsCount); + + foreach (var thread in unreadCounts.UnreadThreads) + { + Debug.Log(thread.ParentMessageId); + Debug.Log(thread.UnreadCount); + Debug.Log(thread.LastRead); + Debug.Log(thread.LastReadMessageId); + } } /// - /// https://getstream.io/chat/docs/unity/threads/?language=unity#unread-count + /// https://getstream.io/chat/docs/unity/threads/?language=unity#thread-manager /// - public void ObserveUnreadThreadsCount() + public async Task TrackThreadsAndStayInSync() { - var localUserData = Client.LocalUserData; + // Get notified when a thread starts or stops being tracked locally + Client.ThreadTracked += thread => { /* a new IStreamThread is now tracked */ }; + Client.ThreadUntracked += thread => { /* an IStreamThread is no longer tracked */ }; - var unreadThreads = localUserData.UnreadThreads; + // Load threads. Watch defaults to true so realtime updates are delivered. + var response = await Client.QueryThreadsAsync(new StreamQueryThreadsRequest + { + Watch = true, + Limit = 10, + }); + + // Each returned IStreamThread is stateful: it is cached and kept in sync with + // realtime events automatically. Subscribe to per-thread events to react to + // changes (e.g. new replies, title / custom data updates, read state changes). + foreach (var thread in response.Threads) + { + thread.Updated += changedThread => { /* title or custom data changed */ }; + thread.ReplyReceived += (changedThread, reply) => { /* new reply arrived */ }; + thread.ReadStateChanged += changedThread => { /* unread state changed */ }; + } } public IStreamChatClient Client { get; private set; } diff --git a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs b/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs deleted file mode 100644 index 4f536736..00000000 --- a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; -using StreamChat.Core; -using StreamChat.Libs.Auth; - -namespace StreamChat.Samples -{ - internal sealed class TokensAndAuthentication - { - /// - /// https://getstream.io/chat/docs/unity/tokens_and_authentication/?language=unity#how-to-refresh-expired-tokens - /// - public async Task ConnectWithTokenProvider() - { - // If your backend exposes a simple endpoint to get token from a url you can use our predefined token provider and provide delegate for URL construction - var tokenProvider = StreamChatClient.CreateDefaultTokenProvider(userId - => new Uri($"https:your-awesome-page.com/api/get_token?userId={userId}")); - await Client.ConnectUserAsync("api-key", "local-user-id", tokenProvider); - - - // For more advanced cases you can implement the ITokenProvider - var yourTokenProvider = new YourTokenProvider(); - await Client.ConnectUserAsync("api-key", "local-user-id", yourTokenProvider); - } - -// You can write your own implementation of the ITokenProvider - public class YourTokenProvider : ITokenProvider - { - public async Task GetTokenAsync(string userId) - { - // Your logic to get the auth token from your backend and generated with Stream backend SDK - var token = await Task.FromResult("some-token"); - return token; - } - } - - private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); - } -} \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta b/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta deleted file mode 100644 index 5c2f7ac9..00000000 --- a/Assets/Plugins/StreamChat/Samples/TokensAndAuthentication.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3d248a08391e4421a4af35441a53e232 -timeCreated: 1671659645 \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs b/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs index e15f9276..a7f3c4c6 100644 --- a/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs +++ b/Assets/Plugins/StreamChat/Samples/UnreadCountsCodeSamples.cs @@ -9,30 +9,61 @@ namespace StreamChat.Samples internal sealed class UnreadCountsCodeSamples { /// - /// https://getstream.io/chat/docs/unity/unread/?language=unity + /// https://getstream.io/chat/docs/unity/unread/?language=unity#reading-unread-counts /// - /// - public async Task GetUnreadCounts() + public async Task ReadingUnreadCounts() { - // Once user is connected you can access unread counts via IStreamLocalUserData - var localUserData = Client.LocalUserData; + // Step 1: Get initial unread counts when connecting + var localUserData = await Client.ConnectUserAsync("api_key", "user_id", "user_token"); Debug.Log(localUserData.UnreadChannels); Debug.Log(localUserData.TotalUnreadCount); - // It's also returned by the ConnectUserAsync method - var localUserData2 = await Client.ConnectUserAsync("api_key", "user_id", "user_token"); - - // And also returned by the Connected event - Client.Connected += ClientOnConnected; - - // All above examples returned the same IStreamLocalUserData object which represents the local user connected to the Stream Chat server + // You can also access unread counts via Client.LocalUserData after connection + // Or subscribe to the Connected event for real-time updates + Client.Connected += (IStreamLocalUserData userData) => + { + Debug.Log(userData.UnreadChannels); + Debug.Log(userData.TotalUnreadCount); + }; } - private void ClientOnConnected(IStreamLocalUserData localUserData) + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-counts---server-side + /// + public async Task GetLatestUnreadCounts() { + var current = await Client.GetLatestUnreadCountsAsync(); + + Debug.Log(current.TotalUnreadCount); // Total unread messages + Debug.Log(current.TotalUnreadThreadsCount); // Total unread threads + + foreach (var unreadChannel in current.UnreadChannels) + { + Debug.Log(unreadChannel.ChannelCid); // CID of the channel with unread messages + Debug.Log(unreadChannel.UnreadCount); // Count of unread messages + Debug.Log(unreadChannel.LastRead); // Datetime of the last read message + } + + foreach (var unreadChannelByType in current.UnreadChannelsByType) + { + Debug.Log(unreadChannelByType.ChannelType); // Channel type + Debug.Log(unreadChannelByType.ChannelCount); // How many channels of this type have unread messages + Debug.Log(unreadChannelByType.UnreadCount); // How many unread messages in all channels of this type + } + + foreach (var unreadThread in current.UnreadThreads) + { + Debug.Log(unreadThread.ParentMessageId); // Message ID of the parent message for this thread + Debug.Log(unreadThread.LastReadMessageId); // Last read message in this thread + Debug.Log(unreadThread.UnreadCount); // Count of unread messages + Debug.Log(unreadThread.LastRead); // Datetime of the last read message + } } + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-read + /// public async Task MarkRead() { IStreamMessage message = null; @@ -40,28 +71,29 @@ public async Task MarkRead() await message.MarkMessageAsLastReadAsync(); } - public async Task ObserveReadState() - { - await Task.CompletedTask; - } - - public void ChannelsReadState() + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-read + /// + public async Task MarkAlreadyReadMessageAsUnread() { + IStreamMessage message = null; IStreamChannel channel = null; + var messageId = "message-id"; - // Every channel maintains a full list of read state for each channel member - foreach (var read in channel.Read) - { - Debug.Log(read.User); // User - Debug.Log(read.UnreadMessages); // How many unread messages - Debug.Log(read.LastRead); // Last read date - } + // Mark the channel containing this message as unread starting from this message + await message.MarkAsUnreadAsync(); + + // Or mark a channel as unread starting from a specific message id + await channel.MarkChannelAsUnreadAsync(messageId); } - public async Task MarkRead2() + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#mark-all-as-read + /// + public async Task MarkAllAsRead() { - IStreamChannel channel = null; IStreamMessage message = null; + IStreamChannel channel = null; // Mark this message as last read await message.MarkMessageAsLastReadAsync(); @@ -70,36 +102,52 @@ public async Task MarkRead2() await channel.MarkChannelReadAsync(); } - public async Task GetCurrentUnreadCounts() + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#read-state---showing-how-far-other-users-have-read + /// + public void ReadState() { - var current = await Client.GetLatestUnreadCountsAsync(); - - Debug.Log(current.TotalUnreadCount); // Total unread messages - Debug.Log(current.TotalUnreadThreadsCount); // Total unread threads + IStreamChannel channel = null; - foreach (var unreadChannel in current.UnreadChannels) + // Every channel maintains a full list of read state for each channel member + foreach (var read in channel.Read) { - Debug.Log(unreadChannel.ChannelCid); // CID of the channel with unread messages - Debug.Log(unreadChannel.UnreadCount); // Count of unread messages - Debug.Log(unreadChannel.LastRead); // Datetime of the last read message + Debug.Log(read.User); // User + Debug.Log(read.UnreadMessages); // How many unread messages + Debug.Log(read.LastRead); // Last read date } + } - foreach (var unreadChannelByType in current.UnreadChannelsByType) - { - Debug.Log(unreadChannelByType.ChannelType); // Channel type - Debug.Log(unreadChannelByType.ChannelCount); // How many channels of this type have unread messages - Debug.Log(unreadChannelByType.UnreadCount); // How many unread messages in all channels of this type - } + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-messages-per-channel + /// + public void UnreadMessagesPerChannel() + { + IStreamChannel channel = null; - foreach (var unreadThread in current.UnreadThreads) + // Every channel maintains a full list of read state for each channel member + foreach (var read in channel.Read) { - Debug.Log(unreadThread.ParentMessageId); // Message ID of the parent message for this thread - Debug.Log(unreadThread.LastReadMessageId); // Last read message in this thread - Debug.Log(unreadThread.UnreadCount); // Count of unread messages - Debug.Log(unreadThread.LastRead); // Datetime of the last read message + Debug.Log(read.User); // User + Debug.Log(read.UnreadMessages); // How many unread messages + Debug.Log(read.LastRead); // Last read date } } - + + /// + /// https://getstream.io/chat/docs/unity/unread/?language=unity#unread-mentions-per-channel + /// + public Task UnreadMentionsPerChannel() + { + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues + return Task.CompletedTask; + } + + /// + /// Retrieve unread counts without first calling . + /// This is useful for surfacing an unread badge in the background without a persistent connection. + /// public async Task GetLatestUnreadCountsInOfflineMode() { // Set authorization credentials @@ -112,4 +160,4 @@ public async Task GetLatestUnreadCountsInOfflineMode() private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient(); } -} \ No newline at end of file +} diff --git a/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs b/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs index 0b8aada5..fa2780dd 100644 --- a/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs +++ b/Assets/Plugins/StreamChat/Samples/UserPresenceCodeExamples.cs @@ -51,7 +51,8 @@ public void SetVisibilityOnConnect() { //StreamTodo: implement https://getstream.io/chat/docs/unity/presence_format/?language=csharp#invisible (second example) - // Will be implemented soon, please send a support ticket if you need this feature + // This feature is not yet available in the Unity SDK. + // Please let us know if you'd like this feature implemented: https://github.com/GetStream/stream-chat-unity/issues } public async void ListeningForPresenceEvents()