1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 05:27:23 +08:00

Implement Private chat

This commit is contained in:
miterosan 2018-04-08 22:12:57 +02:00
parent a70b329155
commit a48ccb5603
9 changed files with 480 additions and 8 deletions

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.IO.Network;
using osu.Game.Online.Chat;
namespace osu.Game.Online.API.Requests
{
public class GetUserMessagesRequest : APIRequest<List<Message>>
{
private long? since;
public GetUserMessagesRequest(long? sinceId = null)
{
since = sinceId;
}
protected override WebRequest CreateWebRequest()
{
var request = base.CreateWebRequest();
if (since.HasValue)
request.AddParameter(@"since", since.Value.ToString());
return request;
}
protected override string Target => @"chat/messages/private";
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
@ -39,12 +40,18 @@ namespace osu.Game.Online.Chat
/// The channels available for the player to join
/// </summary>
public ObservableCollection<ChannelChat> AvailableChannels { get; } = new ObservableCollection<ChannelChat>();
/// <summary>
/// The user chats opened.
/// </summary>
public ObservableCollection<UserChat> OpenedUserChats { get; } = new ObservableCollection<UserChat>();
private APIAccess api;
private readonly Scheduler scheduler;
private ScheduledDelegate fetchMessagesScheduleder;
private GetChannelMessagesRequest fetchChannelMsgReq;
private GetUserMessagesRequest fetchUserMsgReq;
private long? lastChannelMsgId;
private long? lastUserMsgId;
public ChatManager(Scheduler scheduler)
{
@ -55,8 +62,7 @@ namespace osu.Game.Online.Chat
private void currentChatChanged(ChatBase chatBase)
{
if (chatBase is ChannelChat channel && !JoinedChannels.Contains(channel))
JoinedChannels.Add(channel);
JoinedChannels.Add(channel);
}
/// <summary>
@ -127,6 +133,63 @@ namespace osu.Game.Online.Chat
{
if (fetchChannelMsgReq == null)
fetchNewChannelMessages();
if (fetchUserMsgReq == null)
fetchNewUserMessages();
}
private void fetchNewUserMessages()
{
fetchUserMsgReq = new GetUserMessagesRequest(lastUserMsgId);
fetchUserMsgReq.Success += messages =>
{
handleUserMessages(messages);
lastUserMsgId = messages.LastOrDefault()?.Id ?? lastUserMsgId;
fetchUserMsgReq = null;
};
fetchUserMsgReq.Failure += exception => Logger.Error(exception, "Fetching user messages failed.");
api.Queue(fetchUserMsgReq);
}
private void handleUserMessages(IEnumerable<Message> messages)
{
var outgoingMessages = messages.Where(m => m.Sender.Id == api.LocalUser.Value.Id);
var outgoingMessagesGroups = outgoingMessages.GroupBy(m => m.TargetId);
var incomingMessagesGroups = messages.Except(outgoingMessages).GroupBy(m => m.UserId);
foreach (var messageGroup in incomingMessagesGroups)
{
var targetUser = messageGroup.First().Sender;
var chat = OpenedUserChats.FirstOrDefault(c => c.User.Id == targetUser.Id);
if (chat == null)
{
chat = new UserChat(targetUser);
OpenedUserChats.Add(chat);
}
chat.AddNewMessages(messageGroup.ToArray());
var outgoingTargetMessages = outgoingMessagesGroups.FirstOrDefault(g => g.Key == targetUser.Id);
chat.AddNewMessages(outgoingTargetMessages.ToArray());
}
var withoutReplyGroups = outgoingMessagesGroups.Where(g => OpenedUserChats.All(m => m.ChatID != g.Key));
foreach (var withoutReplyGroup in withoutReplyGroups)
{
var getUserRequest = new GetUserRequest(withoutReplyGroup.First().TargetId);
getUserRequest.Success += user =>
{
var chat = new UserChat(user);
chat.AddNewMessages(withoutReplyGroup.ToArray());
OpenedUserChats.Add(chat);
};
api.Queue(getUserRequest);
}
}
private void fetchNewChannelMessages()
@ -135,6 +198,8 @@ namespace osu.Game.Online.Chat
fetchChannelMsgReq.Success += messages =>
{
if (messages == null)
return;
handleChannelMessages(messages);
lastChannelMsgId = messages.LastOrDefault()?.Id ?? lastChannelMsgId;
fetchChannelMsgReq = null;
@ -163,7 +228,13 @@ namespace osu.Game.Online.Chat
channels.Where(channel => defaultChannels.Contains(channel.Name))
.Where(channel => JoinedChannels.All(c => c.ChatID != channel.ChatID))
.ForEach(channel => JoinedChannels.Add(channel));
.ForEach(channel =>
{
JoinedChannels.Add(channel);
var fetchInitialMsgReq = new GetChannelMessagesRequest(new[] {channel}, null);
fetchInitialMsgReq.Success += handleChannelMessages;
api.Queue(fetchInitialMsgReq);
});
fetchNewMessages();
};
@ -185,7 +256,9 @@ namespace osu.Game.Online.Chat
break;
default:
fetchChannelMsgReq?.Cancel();
fetchChannelMsgReq = null;
fetchMessagesScheduleder?.Cancel();
break;
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
public class UserChat : ChatBase
{
public User User { get; }
public UserChat(User user, Message[] messages = null)
{
User = user ?? throw new ArgumentNullException(nameof(user));
if (messages != null) AddNewMessages(messages);
}
public override TargetType Target => TargetType.User;
public override long ChatID => User.Id;
}
}

View File

@ -29,7 +29,6 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Input.Bindings;
using osu.Game.Online.Chat;
using osu.Game.Rulesets.Mods;
using osu.Game.Skinning;
using OpenTK.Graphics;

View File

@ -21,7 +21,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
{
public class ChatTabControl : OsuTabControl<ChannelChat>
public class ChannelTabControl : OsuTabControl<ChannelChat>
{
private const float shear_width = 10;
@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat
private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab;
public ChatTabControl()
public ChannelTabControl()
{
TabContainer.Margin = new MarginPadding { Left = 50 };
TabContainer.Spacing = new Vector2(-shear_width, 0);
@ -51,6 +51,13 @@ namespace osu.Game.Overlays.Chat
ChannelSelectorActive.BindTo(selectorTab.Active);
}
public void DeselectAll()
{
if (SelectedTab != null)
SelectedTab.Active.Value = false;
SelectedTab = null;
}
protected override void AddTabItem(TabItem<ChannelChat> item, bool addToDropdown = true)
{
if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue)

View File

@ -0,0 +1,55 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Chat
{
public class ChatTabItemCloseButton : OsuClickableContainer
{
private readonly SpriteIcon icon;
public ChatTabItemCloseButton()
{
Size = new Vector2(20);
Child = icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.75f),
Icon = FontAwesome.fa_close,
RelativeSizeAxes = Axes.Both,
};
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
protected override bool OnHover(InputState state)
{
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
base.OnHoverLost(state);
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using OpenTK;
namespace osu.Game.Overlays.Chat
{
public class UserChatTabControl : OsuTabControl<UserChat>
{
protected override TabItem<UserChat> CreateTabItem(UserChat value) => new UserChatTabItem(value) { OnRequestClose = tabCloseRequested };
public Action<UserChat> OnRequestLeave;
public UserChatTabControl()
{
TabContainer.Spacing = new Vector2(-10, 0);
TabContainer.Masking = false;
}
protected override void AddTabItem(TabItem<UserChat> item, bool addToDropdown = true)
{
base.AddTabItem(item, addToDropdown);
if (SelectedTab == null)
SelectTab(item);
}
private void tabCloseRequested(TabItem<UserChat> priv)
{
int totalTabs = TabContainer.Count -1; // account for selectorTab
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(priv), 1, totalTabs);
if (priv == SelectedTab && totalTabs > 1)
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
OnRequestLeave?.Invoke(priv.Value);
}
public void DeselectAll()
{
if (SelectedTab != null)
SelectedTab.Active.Value = false;
SelectedTab = null;
}
}
}

View File

@ -0,0 +1,201 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Screens.Menu;
using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Chat
{
public class UserChatTabItem : TabItem<UserChat>
{
private static readonly Vector2 shear = new Vector2(1f / 5f, 0);
public override bool IsRemovable => true;
private readonly Box highlightBox;
private readonly Container backgroundContainer;
private readonly Box backgroundBox;
private readonly OsuSpriteText username;
private readonly ChatTabItemCloseButton closeButton;
public UserChatTabItem(UserChat value)
: base(value)
{
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
Origin = Anchor.BottomRight;
Anchor = Anchor.BottomRight;
EdgeEffect = deactivateEdgeEffect;
Masking = false;
Shear = shear;
Children = new Drawable[]
{
new Container()
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundBox = new Box
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
EdgeSmoothness = new Vector2(1, 0),
},
}
},
highlightBox = new Box
{
Width = 5,
BypassAutoSizeAxes = Axes.X,
Alpha = 0,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
EdgeSmoothness = new Vector2(1, 0),
RelativeSizeAxes = Axes.Y,
Colour = new OsuColour().Yellow
},
new Container
{
Masking = true,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Child = new FlowContainerWithOrigin
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
X = -5,
Direction = FillDirection.Horizontal,
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
Shear = -shear,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Margin = new MarginPadding
{
Horizontal = 5
},
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.fa_eercast,
Origin = Anchor.Centre,
Scale = new Vector2(1.2f),
X = -5,
Y = 5,
Anchor = Anchor.Centre,
Colour = new OsuColour().BlueDarker,
RelativeSizeAxes = Axes.Both,
},
new CircularContainer
{
RelativeSizeAxes = Axes.Y,
Scale = new Vector2(0.95f),
AutoSizeAxes = Axes.X,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Child = new Avatar(value.User)
{
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
}
},
}
},
username = new OsuSpriteText
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Text = value.User.Username,
Margin = new MarginPadding(1),
TextSize = 18,
},
closeButton = new ChatTabItemCloseButton
{
Height = 1,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Action = delegate
{
if (IsRemovable) OnRequestClose?.Invoke(this);
},
},
}
}
}
};
}
public Action<UserChatTabItem> OnRequestClose;
private readonly EdgeEffectParameters activateEdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 30,
Colour = Color4.Black.Opacity(0.3f),
};
protected override void OnActivated()
{
const int activate_length = 1000;
backgroundBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
highlightBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
highlightBox.FadeIn(activate_length, Easing.OutQuint);
username.FadeIn(activate_length, Easing.OutQuint);
username.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
closeButton.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
closeButton.FadeIn(activate_length, Easing.OutQuint);
TweenEdgeEffectTo(activateEdgeEffect, activate_length);
}
private readonly EdgeEffectParameters deactivateEdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.0f),
};
protected override void OnDeactivated()
{
const int deactivate_length = 500;
backgroundBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
highlightBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
highlightBox.FadeOut(deactivate_length, Easing.OutQuint);
username.FadeOut(deactivate_length, Easing.OutQuint);
username.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
closeButton.FadeOut(deactivate_length, Easing.OutQuint);
closeButton.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
TweenEdgeEffectTo(deactivateEdgeEffect, deactivate_length);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
backgroundBox.Colour = Value.User.Colour != null ? OsuColour.FromHex(Value.User.Colour) : colours.BlueDark;
}
}
}

View File

@ -45,7 +45,8 @@ namespace osu.Game.Overlays
public const float TAB_AREA_HEIGHT = 50;
private readonly ChatTabControl channelTabs;
private readonly ChannelTabControl channelTabs;
private readonly UserChatTabControl userTabs;
private readonly Container chatContainer;
private readonly Container tabsArea;
@ -154,17 +155,23 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
channelTabs = new ChatTabControl
channelTabs = new ChannelTabControl
{
RelativeSizeAxes = Axes.Both,
OnRequestLeave = channel => chatManager.JoinedChannels.Remove(channel),
},
userTabs = new UserChatTabControl
{
RelativeSizeAxes = Axes.Both,
OnRequestLeave = privateChat => chatManager.OpenedUserChats.Remove(privateChat),
}
}
},
},
},
};
userTabs.Current.ValueChanged += user => chatManager.CurrentChat.Value = user;
channelTabs.Current.ValueChanged += newChannel => chatManager.CurrentChat.Value = newChannel;
channelTabs.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden;
channelSelection.StateChanged += state =>
@ -241,7 +248,16 @@ namespace osu.Game.Overlays
textbox.Current.Disabled = chat.ReadOnly;
if (chat is ChannelChat channelChat)
{
channelTabs.Current.Value = channelChat;
userTabs.DeselectAll();
}
if (chat is UserChat userChat)
{
userTabs.Current.Value = userChat;
channelTabs.DeselectAll();
}
var loaded = loadedChannels.Find(d => d.Chat == chat);
if (loaded == null)
@ -355,6 +371,23 @@ namespace osu.Game.Overlays
chatManager.CurrentChat.ValueChanged += currentChatChanged;
chatManager.JoinedChannels.CollectionChanged += joinedChannelsChanged;
chatManager.AvailableChannels.CollectionChanged += availableChannelsChanged;
chatManager.OpenedUserChats.CollectionChanged += openedUserChatsChanged;
}
private void openedUserChatsChanged(object sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
userTabs.AddItem(args.NewItems[0] as UserChat);
break;
case NotifyCollectionChangedAction.Remove:
userTabs.RemoveItem(args.OldItems[0] as UserChat);
break;
case NotifyCollectionChangedAction.Reset:
userTabs.Clear();
break;
}
}
private void postMessage(TextBox textbox, bool newText)