Skip to content

Commit

Permalink
Sort messages by CreatedAt after appending new items - this fixes the…
Browse files Browse the repository at this point in the history
… message ordering when using the LoadOlderMessagesAsync() (#120)
  • Loading branch information
sierpinskid authored Apr 19, 2023
1 parent 7a49bf1 commit 1d7d440
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public interface IStreamChannel : IStreamStatefulModel
IStreamChannelMember Membership { get; }

/// <summary>
/// List of channel messages. By default only latest messages are loaded. If you wish to load older messages user the <see cref="LoadOlderMessagesAsync"/>
/// List of channel messages. By default only latest messages are loaded. If you wish to load older messages use the <see cref="LoadOlderMessagesAsync"/>
/// </summary>
IReadOnlyList<IStreamMessage> Messages { get; }

Expand Down Expand Up @@ -283,6 +283,10 @@ public interface IStreamChannel : IStreamStatefulModel
/// </summary>
Task<IStreamMessage> SendNewMessageAsync(StreamSendMessageRequest sendMessageRequest);

/// <summary>
/// Load next portion of older messages. Older messages will be prepended to the <see cref="Messages"/> list.
/// Note that loading older messages does NOT trigger the <see cref="MessageReceived"/> event
/// </summary>
Task LoadOlderMessagesAsync();

/// <summary>
Expand Down
57 changes: 32 additions & 25 deletions Assets/Plugins/StreamChat/Core/StatefulModels/StreamChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ namespace StreamChat.Core.StatefulModels
public delegate void StreamChannelUserChangeHandler(IStreamChannel channel, IStreamUser user);

public delegate void StreamChannelMemberChangeHandler(IStreamChannel channel, IStreamChannelMember member);
public delegate void StreamChannelMemberAnyChangeHandler(IStreamChannel channel, IStreamChannelMember member, OperationType operationType);

public delegate void StreamChannelMemberAnyChangeHandler(IStreamChannel channel, IStreamChannelMember member,
OperationType operationType);

public delegate void StreamMessageReactionHandler(IStreamChannel channel, IStreamMessage message,
StreamReaction reaction);
Expand Down Expand Up @@ -57,7 +59,7 @@ internal sealed class StreamChannel : StreamStatefulModelBase<StreamChannel>,
public event StreamChannelMemberChangeHandler MemberRemoved;

public event StreamChannelMemberChangeHandler MemberUpdated;

public event StreamChannelMemberAnyChangeHandler MembersChanged;

public event StreamChannelVisibilityHandler VisibilityChanged;
Expand Down Expand Up @@ -177,8 +179,7 @@ internal set

#endregion

public bool IsDirectMessage =>
Members.Count == 2 && Members.Any(m => m.User == Client.LocalUserData.User);
public bool IsDirectMessage => Members.Count == 2 && Members.Any(m => m.User == Client.LocalUserData.User);

public Task<IStreamMessage> SendNewMessageAsync(string message)
=> SendNewMessageAsync(new StreamSendMessageRequest
Expand Down Expand Up @@ -286,12 +287,12 @@ public async Task<StreamImageUploadResponse> UploadImageAsync(byte[] imageConten

//StreamTodo: IMPLEMENT, this should probably work like LoadNextMembers, LoadPreviousMembers? what about sorting - in config?
//Perhaps we should have both, maybe user wants to search members and not only paginate joined
public async Task<IEnumerable<IStreamChannelMember>> QueryMembersAsync(IDictionary<string, object> filters = null,
int limit = 30, int offset = 0)
public async Task<IEnumerable<IStreamChannelMember>> QueryMembersAsync(
IDictionary<string, object> filters = null, int limit = 30, int offset = 0)
{
// filter_conditions is required by API but empty object is accepted
filters ??= new Dictionary<string, object>();

var response = await LowLevelClient.InternalChannelApi.QueryMembersAsync(new QueryMembersRequestInternalDTO
{
CreatedAtAfter = null,
Expand Down Expand Up @@ -389,8 +390,7 @@ public Task AddMembersAsync(IEnumerable<IStreamUser> users)
return AddMembersAsync(users.Select(u => u.Id));
}

public Task AddMembersAsync(params IStreamUser[] users)
=> AddMembersAsync(users as IEnumerable<IStreamUser>);
public Task AddMembersAsync(params IStreamUser[] users) => AddMembersAsync(users as IEnumerable<IStreamUser>);

public async Task AddMembersAsync(IEnumerable<string> userIds)
{
Expand All @@ -412,9 +412,8 @@ public async Task AddMembersAsync(IEnumerable<string> userIds)
});
Cache.TryCreateOrUpdate(response);
}

public Task AddMembersAsync(params string[] users)
=> AddMembersAsync(users as IEnumerable<string>);

public Task AddMembersAsync(params string[] users) => AddMembersAsync(users as IEnumerable<string>);

public Task RemoveMembersAsync(IEnumerable<IStreamChannelMember> members)
{
Expand All @@ -424,16 +423,16 @@ public Task RemoveMembersAsync(IEnumerable<IStreamChannelMember> members)

public Task RemoveMembersAsync(params IStreamChannelMember[] members)
=> RemoveMembersAsync(members as IEnumerable<IStreamChannelMember>);

public Task RemoveMembersAsync(IEnumerable<IStreamUser> members)
{
StreamAsserts.AssertNotNull(members, nameof(members));
return RemoveMembersAsync(members.Select(_ => _.Id));
}

public Task RemoveMembersAsync(params IStreamUser[] members)
=> RemoveMembersAsync(members as IEnumerable<IStreamUser>);

public async Task RemoveMembersAsync(IEnumerable<string> userIds)
{
StreamAsserts.AssertNotNull(userIds, nameof(userIds));
Expand All @@ -445,10 +444,9 @@ public async Task RemoveMembersAsync(IEnumerable<string> userIds)
});
Cache.TryCreateOrUpdate(response);
}

public Task RemoveMembersAsync(params string[] userIds)
=> RemoveMembersAsync(userIds as IEnumerable<string>);


public Task RemoveMembersAsync(params string[] userIds) => RemoveMembersAsync(userIds as IEnumerable<string>);

public Task JoinAsMemberAsync() => AddMembersAsync(Client.LocalUserData.User);

public Task LeaveAsMemberChannelAsync() => RemoveMembersAsync(Client.LocalUserData.User);
Expand Down Expand Up @@ -499,19 +497,19 @@ public async Task TruncateAsync(DateTimeOffset? truncatedAt = default, string sy
}

//StreamTodo: write test and check Client.WatchedChannels
public Task StopWatchingAsync() =>
LowLevelClient.InternalChannelApi.StopWatchingChannelAsync(Type, Id,
public Task StopWatchingAsync()
=> LowLevelClient.InternalChannelApi.StopWatchingChannelAsync(Type, Id,
new ChannelStopWatchingRequestInternalDTO());

public Task DeleteAsync()
=> LowLevelClient.InternalChannelApi.DeleteChannelAsync(Type, Id, isHardDelete: false);

//StreamTodo: auto send TypingStopped after timeout + timeout received typing users in case they've lost connection and never sent the stop event
public Task SendTypingStartedEventAsync() =>
LowLevelClient.InternalChannelApi.SendTypingStartEventAsync(Type, Id);
public Task SendTypingStartedEventAsync()
=> LowLevelClient.InternalChannelApi.SendTypingStartEventAsync(Type, Id);

public Task SendTypingStoppedEventAsync() =>
LowLevelClient.InternalChannelApi.SendTypingStopEventAsync(Type, Id);
public Task SendTypingStoppedEventAsync()
=> LowLevelClient.InternalChannelApi.SendTypingStopEventAsync(Type, Id);

public override string ToString() => $"Channel - Id: {Id}, Name: {Name}";

Expand Down Expand Up @@ -543,6 +541,8 @@ void IUpdateableFrom<ChannelStateResponseInternalDTO, StreamChannel>.UpdateFromD

#endregion

SortMessagesByCreatedAt();

//StreamTodo should every UpdateFromDto trigger Updated event?
}

Expand All @@ -564,6 +564,8 @@ void IUpdateableFrom<ChannelStateResponseFieldsInternalDTO, StreamChannel>.Updat
WatcherCount = GetOrDefault(dto.WatcherCount, WatcherCount);
_watchers.TryAppendUniqueTrackedObjects(dto.Watchers, cache.Users);

SortMessagesByCreatedAt();

#endregion
}

Expand Down Expand Up @@ -932,5 +934,10 @@ private Task InternalBanUserAsync(IStreamUser user, bool isShadowBan = false, st
//AdditionalProperties = null
});
}

private void SortMessagesByCreatedAt()
{
_messages.Sort((msg1, msg2) => msg1.CreatedAt.CompareTo(msg2.CreatedAt));
}
}
}

0 comments on commit 1d7d440

Please sign in to comment.