2025-01-07 18:12:31 +08:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-01-09 16:31:01 +08:00
|
|
|
using System.Collections.Specialized;
|
2025-01-07 18:12:31 +08:00
|
|
|
using System.Linq;
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Sprites;
|
2025-01-07 18:32:30 +08:00
|
|
|
using osu.Game.Configuration;
|
2025-01-07 18:12:31 +08:00
|
|
|
using osu.Game.Graphics;
|
|
|
|
using osu.Game.Online.API;
|
|
|
|
using osu.Game.Online.API.Requests.Responses;
|
|
|
|
using osu.Game.Online.Chat;
|
|
|
|
using osu.Game.Online.Metadata;
|
|
|
|
using osu.Game.Overlays;
|
|
|
|
using osu.Game.Overlays.Notifications;
|
|
|
|
using osu.Game.Users;
|
|
|
|
|
|
|
|
namespace osu.Game.Online
|
|
|
|
{
|
|
|
|
public partial class FriendPresenceNotifier : Component
|
|
|
|
{
|
|
|
|
[Resolved]
|
|
|
|
private INotificationOverlay notifications { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private IAPIProvider api { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private MetadataClient metadataClient { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private ChannelManager channelManager { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private ChatOverlay chatOverlay { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private OsuColour colours { get; set; } = null!;
|
|
|
|
|
2025-01-07 18:32:30 +08:00
|
|
|
[Resolved]
|
|
|
|
private OsuConfigManager config { get; set; } = null!;
|
|
|
|
|
|
|
|
private readonly Bindable<bool> notifyOnFriendPresenceChange = new BindableBool();
|
2025-01-09 16:31:01 +08:00
|
|
|
|
|
|
|
private readonly IBindableList<APIRelation> friends = new BindableList<APIRelation>();
|
|
|
|
private readonly IBindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>();
|
|
|
|
|
2025-01-07 18:12:31 +08:00
|
|
|
private readonly HashSet<APIUser> onlineAlertQueue = new HashSet<APIUser>();
|
|
|
|
private readonly HashSet<APIUser> offlineAlertQueue = new HashSet<APIUser>();
|
|
|
|
|
|
|
|
private double? lastOnlineAlertTime;
|
|
|
|
private double? lastOfflineAlertTime;
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
2025-01-07 18:32:30 +08:00
|
|
|
config.BindWith(OsuSetting.NotifyOnFriendPresenceChange, notifyOnFriendPresenceChange);
|
|
|
|
|
2025-01-09 16:31:01 +08:00
|
|
|
friends.BindTo(api.Friends);
|
|
|
|
friends.BindCollectionChanged(onFriendsChanged, true);
|
|
|
|
|
|
|
|
friendStates.BindTo(metadataClient.FriendStates);
|
|
|
|
friendStates.BindCollectionChanged(onFriendStatesChanged, true);
|
2025-01-07 18:12:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
{
|
|
|
|
base.Update();
|
|
|
|
|
|
|
|
alertOnlineUsers();
|
|
|
|
alertOfflineUsers();
|
|
|
|
}
|
|
|
|
|
2025-01-09 16:31:01 +08:00
|
|
|
private void onFriendsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
|
|
{
|
|
|
|
switch (e.Action)
|
|
|
|
{
|
|
|
|
case NotifyCollectionChangedAction.Add:
|
|
|
|
foreach (APIRelation friend in e.NewItems!.Cast<APIRelation>())
|
|
|
|
{
|
|
|
|
if (friend.TargetUser is not APIUser user)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (friendStates.TryGetValue(friend.TargetID, out _))
|
|
|
|
markUserOnline(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
|
|
foreach (APIRelation friend in e.OldItems!.Cast<APIRelation>())
|
|
|
|
{
|
|
|
|
if (friend.TargetUser is not APIUser user)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
onlineAlertQueue.Remove(user);
|
|
|
|
offlineAlertQueue.Remove(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onFriendStatesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e)
|
|
|
|
{
|
|
|
|
switch (e.Action)
|
|
|
|
{
|
|
|
|
case NotifyDictionaryChangedAction.Add:
|
|
|
|
foreach ((int friendId, _) in e.NewItems!)
|
|
|
|
{
|
|
|
|
APIRelation? friend = friends.FirstOrDefault(f => f.TargetID == friendId);
|
|
|
|
|
|
|
|
if (friend?.TargetUser is APIUser user)
|
|
|
|
markUserOnline(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NotifyDictionaryChangedAction.Remove:
|
|
|
|
foreach ((int friendId, _) in e.OldItems!)
|
|
|
|
{
|
|
|
|
APIRelation? friend = friends.FirstOrDefault(f => f.TargetID == friendId);
|
|
|
|
|
|
|
|
if (friend?.TargetUser is APIUser user)
|
|
|
|
markUserOffline(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void markUserOnline(APIUser user)
|
|
|
|
{
|
|
|
|
if (!offlineAlertQueue.Remove(user))
|
|
|
|
{
|
|
|
|
onlineAlertQueue.Add(user);
|
|
|
|
lastOnlineAlertTime ??= Time.Current;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void markUserOffline(APIUser user)
|
|
|
|
{
|
|
|
|
if (!onlineAlertQueue.Remove(user))
|
|
|
|
{
|
|
|
|
offlineAlertQueue.Add(user);
|
|
|
|
lastOfflineAlertTime ??= Time.Current;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-07 18:12:31 +08:00
|
|
|
private void alertOnlineUsers()
|
|
|
|
{
|
|
|
|
if (onlineAlertQueue.Count == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (lastOnlineAlertTime == null || Time.Current - lastOnlineAlertTime < 1000)
|
|
|
|
return;
|
|
|
|
|
2025-01-07 18:32:30 +08:00
|
|
|
if (!notifyOnFriendPresenceChange.Value)
|
|
|
|
{
|
|
|
|
lastOnlineAlertTime = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-07 18:12:31 +08:00
|
|
|
APIUser? singleUser = onlineAlertQueue.Count == 1 ? onlineAlertQueue.Single() : null;
|
|
|
|
|
|
|
|
notifications.Post(new SimpleNotification
|
|
|
|
{
|
|
|
|
Icon = FontAwesome.Solid.UserPlus,
|
|
|
|
Text = $"Online: {string.Join(@", ", onlineAlertQueue.Select(u => u.Username))}",
|
|
|
|
IconColour = colours.Green,
|
|
|
|
Activated = () =>
|
|
|
|
{
|
|
|
|
if (singleUser != null)
|
|
|
|
{
|
|
|
|
channelManager.OpenPrivateChannel(singleUser);
|
|
|
|
chatOverlay.Show();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
onlineAlertQueue.Clear();
|
|
|
|
lastOnlineAlertTime = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void alertOfflineUsers()
|
|
|
|
{
|
|
|
|
if (offlineAlertQueue.Count == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (lastOfflineAlertTime == null || Time.Current - lastOfflineAlertTime < 1000)
|
|
|
|
return;
|
|
|
|
|
2025-01-07 18:32:30 +08:00
|
|
|
if (!notifyOnFriendPresenceChange.Value)
|
|
|
|
{
|
|
|
|
lastOfflineAlertTime = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-07 18:12:31 +08:00
|
|
|
notifications.Post(new SimpleNotification
|
|
|
|
{
|
|
|
|
Icon = FontAwesome.Solid.UserMinus,
|
|
|
|
Text = $"Offline: {string.Join(@", ", offlineAlertQueue.Select(u => u.Username))}",
|
|
|
|
IconColour = colours.Red
|
|
|
|
});
|
|
|
|
|
|
|
|
offlineAlertQueue.Clear();
|
|
|
|
lastOfflineAlertTime = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|