diff --git a/osu-framework b/osu-framework index 21a97586f7..dc4ea5be42 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 21a97586f7fa8d9aa65b4131824151d88a70b520 +Subproject commit dc4ea5be425d37f3a0dd09f6acdf6799d42e3d74 diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs new file mode 100644 index 0000000000..52269d9fe8 --- /dev/null +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class PostMessageRequest : APIRequest + { + private readonly Message message; + + public PostMessageRequest(Message message) + { + this.message = message; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.POST; + req.AddParameter(@"target_type", message.TargetType.GetDescription()); + req.AddParameter(@"target_id", message.TargetId.ToString()); + req.AddParameter(@"message", message.Content); + + return req; + } + + protected override string Target => @"chat/messages"; + } +} \ No newline at end of file diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index d895b93336..04ebf0a389 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; +using osu.Framework.Lists; namespace osu.Game.Online.Chat { @@ -21,7 +23,7 @@ namespace osu.Game.Online.Chat [JsonProperty(@"channel_id")] public int Id; - public List Messages = new List(); + public SortedList Messages = new SortedList((m1, m2) => m1.Id.CompareTo(m2.Id)); //internal bool Joined; @@ -36,7 +38,10 @@ namespace osu.Game.Online.Chat public void AddNewMessages(IEnumerable messages) { + messages = messages.Except(Messages).ToList(); + Messages.AddRange(messages); + purgeOldMessages(); NewMessagesArrived?.Invoke(messages); diff --git a/osu.Game/Online/Chat/Drawables/ChatLine.cs b/osu.Game/Online/Chat/Drawables/ChatLine.cs index bfbcf3d707..59e83fd385 100644 --- a/osu.Game/Online/Chat/Drawables/ChatLine.cs +++ b/osu.Game/Online/Chat/Drawables/ChatLine.cs @@ -55,6 +55,9 @@ namespace osu.Game.Online.Chat.Drawables private Color4 getUsernameColour(Message message) { + if (!string.IsNullOrEmpty(message.Sender?.Colour)) + return OsuColour.FromHex(message.Sender.Colour); + //todo: use User instead of Message when user_id is correctly populated. return username_colours[message.UserId % username_colours.Length]; } @@ -91,7 +94,7 @@ namespace osu.Game.Online.Chat.Drawables new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Text = $@"{Message.User.Username}:", + Text = $@"{Message.Sender.Username}:", Colour = getUsernameColour(Message), TextSize = text_size, Origin = Anchor.TopRight, diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index b267cf63ac..372e43be38 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel; using Newtonsoft.Json; using osu.Game.Users; @@ -10,14 +11,17 @@ namespace osu.Game.Online.Chat public class Message { [JsonProperty(@"message_id")] - public long Id; + public readonly long Id; //todo: this should be inside sender. - [JsonProperty(@"user_id")] + [JsonProperty(@"sender_id")] public int UserId; - [JsonProperty(@"channel_id")] - public int ChannelId; + [JsonProperty(@"target_type")] + public TargetType TargetType; + + [JsonProperty(@"target_id")] + public int TargetId; [JsonProperty(@"timestamp")] public DateTimeOffset Timestamp; @@ -26,11 +30,31 @@ namespace osu.Game.Online.Chat public string Content; [JsonProperty(@"sender")] - public User User; + public User Sender; [JsonConstructor] public Message() { } + + public override bool Equals(object obj) + { + var objMessage = obj as Message; + + return Id == objMessage?.Id; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + } + + public enum TargetType + { + [Description(@"channel")] + Channel, + [Description(@"user")] + User } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cd89f4bdc7..a139c4d6ce 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -194,6 +194,7 @@ namespace osu.Game }; Dependencies.Cache(options); + Dependencies.Cache(chat); Dependencies.Cache(musicController); Dependencies.Cache(notificationManager); Dependencies.Cache(dialogOverlay); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fc12789b05..457611dfab 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -38,6 +38,10 @@ namespace osu.Game.Overlays private APIAccess api; + private const int transition_length = 500; + + private GetMessagesRequest fetchReq; + public ChatOverlay() { RelativeSizeAxes = Axes.X; @@ -82,94 +86,6 @@ namespace osu.Game.Overlays }); } - protected override bool OnFocus(InputState state) - { - //this is necessary as inputTextBox is masked away and therefore can't get focus :( - inputTextBox.TriggerFocus(); - return false; - } - - private void postMessage(TextBox sender, bool newText) - { - var postText = sender.Text; - - if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) - { - //todo: actually send to server - careChannels.FirstOrDefault()?.AddNewMessages(new[] - { - new Message - { - User = api.LocalUser.Value, - Timestamp = DateTimeOffset.Now, - Content = postText - } - }); - } - - sender.Text = string.Empty; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - api.Register(this); - } - - private long? lastMessageId; - - private List careChannels; - - private void addChannel(Channel channel) - { - Add(new DrawableChannel(channel)); - careChannels.Add(channel); - } - - private GetMessagesRequest fetchReq; - - public void FetchNewMessages(APIAccess api) - { - if (fetchReq != null) return; - - fetchReq = new GetMessagesRequest(careChannels, lastMessageId); - fetchReq.Success += delegate (List messages) - { - var ids = messages.Select(m => m.ChannelId).Distinct(); - - //batch messages per channel. - foreach (var id in ids) - careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.ChannelId == id)); - - lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; - - Debug.Write("success!"); - fetchReq = null; - }; - fetchReq.Failure += delegate - { - Debug.Write("failure!"); - fetchReq = null; - }; - - api.Queue(fetchReq); - } - - private const int transition_length = 500; - - protected override void PopIn() - { - MoveToY(0, transition_length, EasingTypes.OutQuint); - FadeIn(transition_length, EasingTypes.OutQuint); - } - - protected override void PopOut() - { - MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); - FadeOut(transition_length, EasingTypes.InSine); - } - public void APIStateChanged(APIAccess api, APIState state) { switch (state) @@ -183,19 +99,46 @@ namespace osu.Game.Overlays } } + protected override bool OnFocus(InputState state) + { + //this is necessary as inputTextBox is masked away and therefore can't get focus :( + inputTextBox.TriggerFocus(); + return false; + } + + protected override void PopIn() + { + MoveToY(0, transition_length, EasingTypes.OutQuint); + FadeIn(transition_length, EasingTypes.OutQuint); + } + + protected override void PopOut() + { + MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); + FadeOut(transition_length, EasingTypes.InSine); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + api.Register(this); + } + + private long? lastMessageId; + + private List careChannels; + private void initializeChannels() { Clear(); careChannels = new List(); - //if (api.State != APIAccess.APIState.Online) - // return; - SpriteText loading; Add(loading = new OsuSpriteText { - Text = @"Loading available channels...", + Text = @"initialising chat...", Anchor = Anchor.Centre, Origin = Anchor.Centre, TextSize = 40, @@ -211,15 +154,85 @@ namespace osu.Game.Overlays Scheduler.Add(delegate { loading.FadeOut(100); - addChannel(channels.Find(c => c.Name == @"#osu")); + addChannel(channels.Find(c => c.Name == @"#lazer")); }); - //addChannel(channels.Find(c => c.Name == @"#lobby")); - //addChannel(channels.Find(c => c.Name == @"#english")); - - messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); + messageRequest = Scheduler.AddDelayed(fetchNewMessages, 1000, true); }; api.Queue(req); } + + private void addChannel(Channel channel) + { + Add(new DrawableChannel(channel)); + careChannels.Add(channel); + } + + private void fetchNewMessages() + { + if (fetchReq != null) return; + + fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq.Success += delegate (List messages) + { + var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct(); + + //batch messages per channel. + foreach (var id in ids) + careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id)); + + lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; + + Debug.Write("success!"); + fetchReq = null; + }; + fetchReq.Failure += delegate + { + Debug.Write("failure!"); + fetchReq = null; + }; + + api.Queue(fetchReq); + } + + private void postMessage(TextBox textbox, bool newText) + { + var postText = textbox.Text; + + if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) + { + var currentChannel = careChannels.FirstOrDefault(); + + if (currentChannel == null) return; + + var message = new Message + { + Sender = api.LocalUser.Value, + Timestamp = DateTimeOffset.Now, + TargetType = TargetType.Channel, //TODO: read this from currentChannel + TargetId = currentChannel.Id, + Content = postText + }; + + textbox.ReadOnly = true; + var req = new PostMessageRequest(message); + + req.Failure += e => + { + textbox.FlashColour(Color4.Red, 1000); + textbox.ReadOnly = false; + }; + + req.Success += m => + { + currentChannel.AddNewMessages(new[] { m }); + + textbox.ReadOnly = false; + textbox.Text = string.Empty; + }; + + api.Queue(req); + } + } } } diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 1fd19af557..a5074100c7 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -72,6 +72,7 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarChatButton(), new ToolbarMusicButton(), new ToolbarButton { diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs new file mode 100644 index 0000000000..ca612662e1 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + internal class ToolbarChatButton : ToolbarOverlayToggleButton + { + public ToolbarChatButton() + { + Icon = FontAwesome.fa_comments; + } + + [BackgroundDependencyLoader] + private void load(ChatOverlay chat) + { + StateContainer = chat; + Action = chat.ToggleVisibility; + } + } +} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fe331c6356..54fde88b4f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -72,6 +72,8 @@ + +