diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f831266b1b..8c5a2e68ef 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -12,6 +12,14 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; +using osu.Game.Overlays.Notifications; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Colour; +using osu.Game.Online.API; +using osu.Game.Configuration; +using osu.Framework.Bindables; +using osu.Game.Users; namespace osu.Game.Overlays.Chat { @@ -20,6 +28,22 @@ namespace osu.Game.Overlays.Chat public readonly Channel Channel; protected ChatLineContainer ChatLineFlow; private OsuScrollContainer scroll; + public ColourInfo HighlightColour { get; set; } + + [Resolved(CanBeNull = true)] + private NotificationOverlay notificationOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + private Bindable notifyOnMention; + private Bindable notifyOnChat; + private Bindable highlightWords; + private Bindable ignoreList; + private Bindable localUser; public DrawableChannel(Channel channel) { @@ -28,8 +52,15 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours, OsuConfigManager config, IAPIProvider api) { + notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); + notifyOnChat = config.GetBindable(OsuSetting.ChatMessageNotification); + highlightWords = config.GetBindable(OsuSetting.HighlightWords); + ignoreList = config.GetBindable(OsuSetting.IgnoreList); + localUser = api.LocalUser; + HighlightColour = colours.Blue; + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -77,10 +108,14 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); + var ignoredWords = getWords(ignoreList.Value); + var displayMessages = newMessages.Where(m => hasCaseInsensitive(getWords(m.Content), ignoredWords) == null); + displayMessages = displayMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); + checkForMentions(displayMessages); + if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); @@ -96,6 +131,63 @@ namespace osu.Game.Overlays.Chat } } + private void checkForMentions(IEnumerable messages) + { + // only send notifications when chat overlay is **closed** + if (chatOverlay?.IsPresent == true && channelManager?.CurrentChannel.Value == Channel) + return; + + foreach (var message in messages) + { + var words = getWords(message.Content); + var username = localUser.Value.Username; + + if (message.Sender.Username == username) + continue; + + if (notifyOnChat.Value && Channel.Type == ChannelType.PM) + { + var notification = new MentionNotification(Channel, message.Sender.Username, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }, true); + + notificationOverlay?.Post(notification); + continue; + } + + if (notifyOnMention.Value && anyCaseInsensitive(words, username)) + { + var notification = new MentionNotification(Channel, message.Sender.Username, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }, false); + + notificationOverlay?.Post(notification); + continue; + } + + if (!string.IsNullOrWhiteSpace(highlightWords.Value)) + { + var matchedWord = hasCaseInsensitive(words, getWords(highlightWords.Value)); + + if (matchedWord != null) + { + var notification = new MentionNotification(Channel, message.Sender.Username, matchedWord, () => + { + channelManager.CurrentChannel.Value = Channel; + HighlightMessage(message); + }); + + notificationOverlay?.Post(notification); + continue; + } + } + } + } + private void pendingMessageResolved(Message existing, Message updated) { var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); @@ -110,13 +202,31 @@ namespace osu.Game.Overlays.Chat } } - private void messageRemoved(Message removed) + public void HighlightMessage(Message message) { - ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + var chatLine = findChatLine(message); + scroll.ScrollTo(chatLine); + chatLine.FlashColour(HighlightColour, 5000, Easing.InExpo); } + private void messageRemoved(Message removed) + { + findChatLine(removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + } + + private ChatLine findChatLine(Message message) => ChatLineFlow.Children.FirstOrDefault(c => c.Message == message); + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); + private string[] getWords(string input) => input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + /// + /// Finds the first matching string/word in both and (case-insensitive) + /// + private string hasCaseInsensitive(IEnumerable x, IEnumerable y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2)); + + private bool anyCaseInsensitive(IEnumerable x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.InvariantCultureIgnoreCase)); + protected class ChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) @@ -127,5 +237,54 @@ namespace osu.Game.Overlays.Chat return xC.Message.CompareTo(yC.Message); } } + + private class MentionNotification : SimpleNotification + { + public MentionNotification(Channel channel, string username, Action onClick, bool isPm) : this(channel, onClick) + { + if (isPm) + { + Icon = FontAwesome.Solid.Envelope; + Text = $"You received a private message from '{username}'. Click to read it!"; + } + else + { + Icon = FontAwesome.Solid.At; + Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + } + } + + public MentionNotification(Channel channel, string highlighter, string word, Action onClick) : this(channel, onClick) + { + Icon = FontAwesome.Solid.Highlighter; + Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!"; + } + + private MentionNotification(Channel channel, Action onClick) + { + Channel = channel; + this.onClick = onClick; + } + + private readonly Action onClick; + + public Channel Channel { get; } + + public override bool IsImportant => false; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay, ChatOverlay chatOverlay) + { + IconBackgound.Colour = colours.PurpleDark; + Activated = delegate + { + notificationOverlay.Hide(); + chatOverlay.Show(); + onClick?.Invoke(); + + return true; + }; + } + } } }