osu/osu.Game/Online/Chat/Channel.cs

217 lines
6.9 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Lists;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Chat;
namespace osu.Game.Online.Chat
{
public class Channel
{
public const int MAX_HISTORY = 300;
/// <summary>
/// Contains every joined user except the current logged in user. Currently only returned for PM channels.
/// </summary>
public readonly ObservableCollection<APIUser> Users = new ObservableCollection<APIUser>();
[JsonProperty(@"users")]
private int[] userIds
{
set
{
foreach (int id in value)
Users.Add(new APIUser { Id = id });
}
}
/// <summary>
/// Contains all the messages send in the channel.
/// </summary>
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
/// <summary>
/// Contains all the messages that weren't read by the user.
/// </summary>
public IEnumerable<Message> UnreadMessages => Messages.Where(m => LastReadId < m.Id);
/// <summary>
/// Contains all the messages that are still pending for submission to the server.
/// </summary>
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
/// <summary>
/// An event that fires when new messages arrived.
/// </summary>
public event Action<IEnumerable<Message>> NewMessagesArrived;
/// <summary>
/// An event that fires when a pending message gets resolved.
/// </summary>
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
/// <summary>
/// An event that fires when a pending message gets removed.
/// </summary>
public event Action<Message> MessageRemoved;
public bool ReadOnly => false; // todo: not yet used.
public override string ToString() => Name;
[JsonProperty(@"name")]
public string Name;
[JsonProperty(@"description")]
public string Topic;
[JsonProperty(@"type")]
public ChannelType Type;
[JsonProperty(@"channel_id")]
public long Id;
[JsonProperty(@"last_message_id")]
public long? LastMessageId;
[JsonProperty(@"last_read_id")]
public long? LastReadId;
/// <summary>
/// Signals if the current user joined this channel or not. Defaults to false.
/// Note that this does not guarantee a join has completed. Check Id > 0 for confirmation.
/// </summary>
public Bindable<bool> Joined = new Bindable<bool>();
/// <summary>
/// Signals if there is a message to highlight.
/// This is automatically cleared by the associated <see cref="DrawableChannel"/> after highlighting.
/// </summary>
public Bindable<Message> HighlightedMessage = new Bindable<Message>();
/// <summary>
/// The current text box message while in this <see cref="Channel"/>.
/// </summary>
public Bindable<string> TextBoxMessage = new Bindable<string>(string.Empty);
[JsonConstructor]
public Channel()
{
}
/// <summary>
/// Create a private messaging channel with the specified user.
/// </summary>
/// <param name="user">The user to create the private conversation with.</param>
public Channel(APIUser user)
{
Type = ChannelType.PM;
Users.Add(user);
Name = user.Username;
}
/// <summary>
/// Adds the argument message as a local echo. When this local echo is resolved <see cref="PendingMessageResolved"/> will get called.
/// </summary>
/// <param name="message"></param>
public void AddLocalEcho(LocalEchoMessage message)
{
pendingMessages.Add(message);
Messages.Add(message);
NewMessagesArrived?.Invoke(new[] { message });
}
public bool MessagesLoaded;
/// <summary>
/// Adds new messages to the channel and purges old messages. Triggers the <see cref="NewMessagesArrived"/> event.
/// </summary>
/// <param name="messages"></param>
public void AddNewMessages(params Message[] messages)
{
foreach (var m in messages)
{
LocalEchoMessage localEcho = pendingMessages.FirstOrDefault(local => local.Uuid == m.Uuid);
if (localEcho != null)
ReplaceMessage(localEcho, m);
}
messages = messages.Except(Messages).ToArray();
if (messages.Length == 0) return;
Messages.AddRange(messages);
long? maxMessageId = messages.Max(m => m.Id);
if (maxMessageId > LastMessageId)
LastMessageId = maxMessageId;
purgeOldMessages();
NewMessagesArrived?.Invoke(messages);
}
public void RemoveMessagesFromUser(int userId)
{
for (int i = 0; i < Messages.Count; i++)
{
var message = Messages[i];
if (message.SenderId == userId)
{
Messages.RemoveAt(i--);
MessageRemoved?.Invoke(message);
}
}
}
/// <summary>
/// Replace or remove a message from the channel.
/// </summary>
/// <param name="echo">The local echo message (client-side).</param>
/// <param name="final">The response message, or null if the message became invalid.</param>
public void ReplaceMessage(LocalEchoMessage echo, Message final)
{
if (!pendingMessages.Remove(echo))
throw new InvalidOperationException("Attempted to remove echo that wasn't present");
Messages.Remove(echo);
if (final == null)
{
MessageRemoved?.Invoke(echo);
return;
}
if (Messages.Contains(final))
throw new InvalidOperationException("Attempted to add the same message again");
Messages.Add(final);
if (final.Id > LastMessageId)
LastMessageId = final.Id;
PendingMessageResolved?.Invoke(echo, final);
}
private void purgeOldMessages()
{
// never purge local echos
int messageCount = Messages.Count - pendingMessages.Count;
if (messageCount > MAX_HISTORY)
Messages.RemoveRange(0, messageCount - MAX_HISTORY);
}
}
}