1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 15:33:02 +08:00

Extract all pieces of local user-related state to APIAccess subcomponent

Something I've asked to be done for a long time. Relevant because I've
complained about this on every addition of a new piece of user-local
state: friends, blocks, and now favourite beatmaps.

It's just so messy managing all this inside `APIAccess` next to
everything else, IMO.
This commit is contained in:
Bartłomiej Dach
2025-10-23 10:53:58 +02:00
Unverified
parent dcb30ed5b3
commit 5eda9a0fd7
24 changed files with 239 additions and 183 deletions
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Components
};
for (int i = 1; i <= 100; i++)
((DummyAPIAccess)API).Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
((DummyAPIAccess)API).LocalUserState.Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
});
[Test]
@@ -75,7 +75,9 @@ namespace osu.Game.Tests.Visual.Components
});
AddUntilStep("chat overlay opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddUntilStep("user channel selected", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo(((DummyAPIAccess)API).Friends[0].TargetUser!.Username));
AddUntilStep("user channel selected",
() => channelManager.CurrentChannel.Value.Name,
() => Is.EqualTo(((DummyAPIAccess)API).LocalUserState.Friends[0].TargetUser!.Username));
}
[Test]
@@ -90,8 +90,8 @@ namespace osu.Game.Tests.Visual.Gameplay
var api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.Add(new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.Add(new APIRelation
{
Mutual = true,
RelationType = RelationType.Friend,
@@ -129,8 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
var api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.Add(new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.Add(new APIRelation
{
Mutual = true,
RelationType = RelationType.Friend,
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Online
if (supportLevel > 3)
supportLevel = 0;
((DummyAPIAccess)API).Friends.Add(new APIRelation
((DummyAPIAccess)API).LocalUserState.Friends.Add(new APIRelation
{
TargetID = 2,
RelationType = RelationType.Friend,
@@ -59,8 +59,8 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
@@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("remove one friend", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.RemoveAt(0);
api.LocalUserState.Friends.RemoveAt(0);
});
waitForLoad();
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("add one friend", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.AddRange(getUsers().Take(1).Select(u => new APIRelation
api.LocalUserState.Friends.AddRange(getUsers().Take(1).Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
@@ -101,8 +101,8 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
@@ -130,8 +130,8 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
@@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("bring a friend online", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[0].TargetID, new UserPresence { Status = UserStatus.Online });
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[0].TargetID, new UserPresence { Status = UserStatus.Online });
});
assertVisiblePanelCount<UserPanel>(1);
@@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("bring a friend online", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, new UserPresence { Status = UserStatus.Online });
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[1].TargetID, new UserPresence { Status = UserStatus.Online });
});
assertVisiblePanelCount<UserPanel>(1);
@@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("take friend offline", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, null);
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[1].TargetID, null);
});
assertVisiblePanelCount<UserPanel>(1);
@@ -184,8 +184,8 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
@@ -443,7 +443,7 @@ namespace osu.Game.Tests.Visual.Online
Task.Run(() =>
{
requestLock.Wait(3000);
dummyAPI.Friends.Add(apiRelation);
dummyAPI.LocalUserState.Friends.Add(apiRelation);
req.TriggerSuccess(new AddFriendResponse
{
UserRelation = apiRelation
@@ -453,11 +453,11 @@ namespace osu.Game.Tests.Visual.Online
return true;
};
});
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
AddStep("clear friend list", () => dummyAPI.LocalUserState.Friends.Clear());
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
AddStep("Complete request", () => requestLock.Set());
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
AddUntilStep("Friend added", () => API.LocalUserState.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
}
[Test]
@@ -486,7 +486,7 @@ namespace osu.Game.Tests.Visual.Online
Task.Run(() =>
{
requestLock.Wait(3000);
dummyAPI.Friends.Add(apiRelation);
dummyAPI.LocalUserState.Friends.Add(apiRelation);
req.TriggerSuccess(new AddFriendResponse
{
UserRelation = apiRelation
@@ -496,11 +496,11 @@ namespace osu.Game.Tests.Visual.Online
return true;
};
});
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
AddStep("clear friend list", () => dummyAPI.LocalUserState.Friends.Clear());
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
AddStep("Complete request", () => requestLock.Set());
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
AddUntilStep("Friend added", () => API.LocalUserState.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
}
}
}
@@ -50,8 +50,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set 10 friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = i,
@@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set 20 friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 20).Select(i => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 20).Select(i => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = i,
@@ -78,8 +78,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set 10 friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
api.LocalUserState.Friends.Clear();
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = i,
+13 -109
View File
@@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
@@ -18,7 +17,7 @@ using osu.Framework.Development;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Localisation;
@@ -26,11 +25,10 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Users;
namespace osu.Game.Online.API
{
public partial class APIAccess : Component, IAPIProvider
public partial class APIAccess : CompositeComponent, IAPIProvider
{
private readonly OsuGameBase game;
private readonly OsuConfigManager config;
@@ -53,30 +51,23 @@ namespace osu.Game.Online.API
public string ProvidedUsername { get; private set; }
public SessionVerificationMethod? SessionVerificationMethod { get; set; }
public SessionVerificationMethod? SessionVerificationMethod { get; private set; }
public string SecondFactorCode { get; private set; }
private string password;
public IBindable<APIUser> LocalUser => localUser;
public IBindableList<APIRelation> Friends => friends;
public IBindableList<APIRelation> Blocks => blocks;
public IBindable<APIUser> LocalUser => localUserState.User;
public ILocalUserState LocalUserState => localUserState;
private readonly LocalUserState localUserState;
public INotificationsClient NotificationsClient { get; }
public Language Language => game.CurrentLanguage.Value;
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>();
private BindableList<APIRelation> blocks { get; } = new BindableList<APIRelation>();
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
private readonly Bindable<UserStatus> configStatus = new Bindable<UserStatus>();
private readonly Bindable<bool> configSupporter = new Bindable<bool>();
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
private readonly Logger log;
@@ -108,13 +99,12 @@ namespace osu.Game.Online.API
authentication.TokenString = config.Get<string>(OsuSetting.Token);
authentication.Token.ValueChanged += onTokenChanged;
config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
config.BindWith(OsuSetting.WasSupporter, configSupporter);
AddInternal(localUserState = new LocalUserState(this, config));
if (HasLogin)
{
// Early call to ensure the local user / "logged in" state is correct immediately.
setPlaceholderLocalUser();
localUserState.SetPlaceholderLocalUser(ProvidedUsername);
// This is required so that Queue() requests during startup sequence don't fail due to "not logged in".
state.Value = APIState.Connecting;
@@ -249,8 +239,8 @@ namespace osu.Game.Online.API
/// <returns>Whether the connection attempt was successful.</returns>
private void attemptConnect()
{
if (localUser.IsDefault)
Scheduler.Add(setPlaceholderLocalUser, false);
if (LocalUser.IsDefault)
Scheduler.Add(localUserState.SetPlaceholderLocalUser, ProvidedUsername, false);
// save the username at this point, if the user requested for it to be.
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
@@ -348,8 +338,7 @@ namespace osu.Game.Online.API
{
Debug.Assert(ThreadSafety.IsUpdateThread);
localUser.Value = me;
configSupporter.Value = me.IsSupporter;
localUserState.SetLocalUser(me);
SessionVerificationMethod = me.SessionVerificationMethod;
state.Value = SessionVerificationMethod == null ? APIState.Online : APIState.RequiresSecondFactorAuth;
failureCount = 0;
@@ -365,8 +354,6 @@ namespace osu.Game.Online.API
}
}
UpdateLocalFriends();
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
// before actually going online.
@@ -374,23 +361,6 @@ namespace osu.Game.Online.API
Thread.Sleep(500);
}
/// <summary>
/// Show a placeholder user if saved credentials are available.
/// This is useful for storing local scores and showing a placeholder username after starting the game,
/// until a valid connection has been established.
/// </summary>
private void setPlaceholderLocalUser()
{
if (!localUser.IsDefault)
return;
localUser.Value = new APIUser
{
Username = ProvidedUsername,
IsSupporter = configSupporter.Value,
};
}
public void Perform(APIRequest request)
{
try
@@ -619,78 +589,12 @@ namespace osu.Game.Online.API
SecondFactorCode = null;
authentication.Clear();
// Reset the status to be broadcast on the next login, in case multiple players share the same system.
configStatus.Value = UserStatus.Online;
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
Schedule(() =>
{
localUser.Value = createGuestUser();
configSupporter.Value = false;
friends.Clear();
});
localUserState.ClearLocalUser();
state.Value = APIState.Offline;
flushQueue();
}
public void UpdateLocalFriends()
{
if (!IsLoggedIn)
return;
var friendsReq = new GetFriendsRequest();
friendsReq.Failure += ex =>
{
if (ex is not WebRequestFlushedException)
state.Value = APIState.Failing;
};
friendsReq.Success += res =>
{
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
var updatedFriends = res.Select(f => f.TargetID).ToHashSet();
// Add new friends into local list.
friends.AddRange(res.Where(r => !existingFriends.Contains(r.TargetID)));
// Remove non-friends from local list.
friends.RemoveAll(f => !updatedFriends.Contains(f.TargetID));
};
Queue(friendsReq);
}
public void UpdateLocalBlocks()
{
if (!IsLoggedIn)
return;
var blocksReq = new GetBlocksRequest();
blocksReq.Failure += ex =>
{
if (ex is not WebRequestFlushedException)
state.Value = APIState.Failing;
};
blocksReq.Success += res =>
{
var existingBlocks = blocks.Select(f => f.TargetID).ToHashSet();
var updatedBlocks = res.Select(f => f.TargetID).ToHashSet();
// Add new blocked users to local list.
blocks.AddRange(res.Where(r => !existingBlocks.Contains(r.TargetID)));
// Remove non-blocked users from local list.
blocks.RemoveAll(b => !updatedBlocks.Contains(b.TargetID));
// Remove friends who got blocked since last check.
friends.RemoveAll(f => updatedBlocks.Contains(f.TargetID));
};
Queue(blocksReq);
}
private static APIUser createGuestUser() => new GuestUser();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
+28 -11
View File
@@ -20,14 +20,11 @@ namespace osu.Game.Online.API
{
public const int DUMMY_USER_ID = 1001;
public Bindable<APIUser> LocalUser { get; } = new Bindable<APIUser>(new APIUser
{
Username = @"Local user",
Id = DUMMY_USER_ID,
});
public DummyLocalUserState LocalUserState { get; } = new DummyLocalUserState();
public Bindable<APIUser> LocalUser => LocalUserState.User;
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
public BindableList<APIRelation> Blocks { get; } = new BindableList<APIRelation>();
ILocalUserState IAPIProvider.LocalUserState => LocalUserState;
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
@@ -208,10 +205,6 @@ namespace osu.Game.Online.API
public void SetState(APIState newState) => state.Value = newState;
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
IBindableList<APIRelation> IAPIProvider.Blocks => Blocks;
/// <summary>
/// Skip 2FA requirement for next login.
/// </summary>
@@ -234,5 +227,29 @@ namespace osu.Game.Online.API
// Ensure (as much as we can) that any pending tasks are run.
Scheduler.Update();
}
public class DummyLocalUserState : ILocalUserState
{
public Bindable<APIUser> User { get; } = new Bindable<APIUser>(new APIUser
{
Username = @"Local user",
Id = DUMMY_USER_ID,
});
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
public BindableList<APIRelation> Blocks { get; } = new BindableList<APIRelation>();
IBindable<APIUser> ILocalUserState.User => User;
IBindableList<APIRelation> ILocalUserState.Friends => Friends;
IBindableList<APIRelation> ILocalUserState.Blocks => Blocks;
public void UpdateFriends()
{
}
public void UpdateBlocks()
{
}
}
}
}
+4 -17
View File
@@ -19,14 +19,11 @@ namespace osu.Game.Online.API
IBindable<APIUser> LocalUser { get; }
/// <summary>
/// The user's friends.
/// The local user's current state.
/// Contains auxiliary information such as the user's friends, blocks, and favourites,
/// as well as methods to manage those in a way that keeps this state consistent throughout the game.
/// </summary>
IBindableList<APIRelation> Friends { get; }
/// <summary>
/// The users blocked by the local user.
/// </summary>
IBindableList<APIRelation> Blocks { get; }
ILocalUserState LocalUserState { get; }
/// <summary>
/// The language supplied by this provider to API requests.
@@ -123,16 +120,6 @@ namespace osu.Game.Online.API
/// </summary>
void Logout();
/// <summary>
/// Update the friends status of the current user.
/// </summary>
void UpdateLocalFriends();
/// <summary>
/// Update the list of users blocked by the current user.
/// </summary>
void UpdateLocalBlocks();
/// <summary>
/// Schedule a callback to run on the update thread.
/// </summary>
+18
View File
@@ -0,0 +1,18 @@
// 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 osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API
{
public interface ILocalUserState
{
IBindable<APIUser> User { get; }
IBindableList<APIRelation> Friends { get; }
IBindableList<APIRelation> Blocks { get; }
void UpdateFriends();
void UpdateBlocks();
}
}
+128
View File
@@ -0,0 +1,128 @@
// 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.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
namespace osu.Game.Online.API
{
public partial class LocalUserState : Component, ILocalUserState
{
public IBindable<APIUser> User => localUser;
public IBindableList<APIRelation> Friends => friends;
public IBindableList<APIRelation> Blocks => blocks;
private readonly IAPIProvider api;
private readonly Bindable<APIUser> localUser = new Bindable<APIUser>(createGuestUser());
private readonly BindableList<APIRelation> friends = new BindableList<APIRelation>();
private readonly BindableList<APIRelation> blocks = new BindableList<APIRelation>();
private readonly Bindable<UserStatus> configStatus = new Bindable<UserStatus>();
private readonly Bindable<bool> configSupporter = new Bindable<bool>();
public LocalUserState(IAPIProvider api, OsuConfigManager config)
{
this.api = api;
config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
config.BindWith(OsuSetting.WasSupporter, configSupporter);
}
#region Logging in / out
private static APIUser createGuestUser() => new GuestUser();
/// <summary>
/// Show a placeholder user if saved credentials are available.
/// This is useful for storing local scores and showing a placeholder username after starting the game,
/// until a valid connection has been established.
/// </summary>
public void SetPlaceholderLocalUser(string username)
{
if (!localUser.IsDefault)
return;
localUser.Value = new APIUser
{
Username = username,
IsSupporter = configSupporter.Value,
};
}
public void SetLocalUser(APIMe me)
{
localUser.Value = me;
configSupporter.Value = me.IsSupporter;
UpdateFriends();
UpdateBlocks();
}
public void ClearLocalUser()
{
// Reset the status to be broadcast on the next login, in case multiple players share the same system.
configStatus.Value = UserStatus.Online;
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
Schedule(() =>
{
localUser.Value = createGuestUser();
configSupporter.Value = false;
friends.Clear();
});
}
#endregion
public void UpdateFriends()
{
if (!api.IsLoggedIn)
return;
var friendsReq = new GetFriendsRequest();
friendsReq.Success += res =>
{
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
var updatedFriends = res.Select(f => f.TargetID).ToHashSet();
// Add new friends into local list.
friends.AddRange(res.Where(r => !existingFriends.Contains(r.TargetID)));
// Remove non-friends from local list.
friends.RemoveAll(f => !updatedFriends.Contains(f.TargetID));
};
api.Queue(friendsReq);
}
public void UpdateBlocks()
{
if (!api.IsLoggedIn)
return;
var blocksReq = new GetBlocksRequest();
blocksReq.Success += res =>
{
var existingBlocks = blocks.Select(f => f.TargetID).ToHashSet();
var updatedBlocks = res.Select(f => f.TargetID).ToHashSet();
// Add new blocked users to local list.
blocks.AddRange(res.Where(r => !existingBlocks.Contains(r.TargetID)));
// Remove non-blocked users from local list.
blocks.RemoveAll(b => !updatedBlocks.Contains(b.TargetID));
// Remove friends who got blocked since last check.
friends.RemoveAll(f => updatedBlocks.Contains(f.TargetID));
};
api.Queue(blocksReq);
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ namespace osu.Game.Online
config.BindWith(OsuSetting.NotifyOnFriendPresenceChange, notifyOnFriendPresenceChange);
friends.BindTo(api.Friends);
friends.BindTo(api.LocalUserState.Friends);
friends.BindCollectionChanged(onFriendsChanged, true);
friendPresences.BindTo(metadataClient.FriendPresences);
@@ -103,7 +103,7 @@ namespace osu.Game.Online.Leaderboards
private void load(IAPIProvider api, OsuColour colour)
{
var user = Score.User;
bool isUserFriend = api.Friends.Any(friend => friend.TargetID == user.OnlineID);
bool isUserFriend = api.LocalUserState.Friends.Any(friend => friend.TargetID == user.OnlineID);
statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList();
@@ -212,7 +212,7 @@ namespace osu.Game.Overlays.Chat
items.Add(new OsuMenuItemSpacer());
items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested));
items.Add(api.Blocks.Any(b => b.TargetID == user.OnlineID)
items.Add(api.LocalUserState.Blocks.Any(b => b.TargetID == user.OnlineID)
? new OsuMenuItem(UsersStrings.BlocksButtonUnblock, MenuItemType.Standard, () => dialogOverlay?.Push(ConfirmBlockActionDialog.Unblock(user)))
: new OsuMenuItem(UsersStrings.BlocksButtonBlock, MenuItemType.Destructive, () => dialogOverlay?.Push(ConfirmBlockActionDialog.Block(user))));
@@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
base.LoadComplete();
apiFriends.BindTo(api.Friends);
apiFriends.BindTo(api.LocalUserState.Friends);
apiFriends.BindCollectionChanged((_, _) => reloadList());
userListToolbar.DisplayStyle.BindValueChanged(_ => reloadList(), true);
@@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
base.LoadComplete();
apiFriends.BindTo(api.Friends);
apiFriends.BindTo(api.LocalUserState.Friends);
apiFriends.BindCollectionChanged((_, _) => updateCounts());
friendPresences.BindTo(metadataClient.FriendPresences);
@@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
status.Value = FriendStatus.None;
}
api.UpdateLocalFriends();
api.LocalUserState.UpdateFriends();
HideLoadingLayer();
};
@@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
base.LoadComplete();
apiFriends.BindTo(api.Friends);
apiFriends.BindTo(api.LocalUserState.Friends);
apiFriends.BindCollectionChanged((_, _) => Schedule(updateStatus));
User.BindValueChanged(u =>
@@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
Background.Colour = colourProvider.Background6;
bool userBlocked = api.Blocks.Any(b => b.TargetID == user.Id);
bool userBlocked = api.LocalUserState.Blocks.Any(b => b.TargetID == user.Id);
AllowableAnchors = [Anchor.BottomCentre, Anchor.TopCentre];
@@ -164,7 +164,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
if (s.UserID == api.LocalUser.Value.Id)
highlightType = BeatmapLeaderboardScore.HighlightType.Own;
else if (api.Friends.Any(r => r.TargetID == s.UserID))
else if (api.LocalUserState.Friends.Any(r => r.TargetID == s.UserID))
highlightType = BeatmapLeaderboardScore.HighlightType.Friend;
return new BeatmapLeaderboardScore(s, sheared: false)
@@ -458,7 +458,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
bool isUserOnline() => metadataClient?.GetPresence(User.OnlineID) != null;
bool canInviteUser() => isUserOnline() && multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true;
bool isUserBlocked() => api.Blocks.Any(b => b.TargetID == User.OnlineID);
bool isUserBlocked() => api.LocalUserState.Blocks.Any(b => b.TargetID == User.OnlineID);
}
}
}
@@ -303,7 +303,7 @@ namespace osu.Game.Screens.Play.HUD
{
base.LoadComplete();
isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.TargetID);
isFriend = User != null && api.LocalUserState.Friends.Any(u => User.OnlineID == u.TargetID);
scoreDisplayMode = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode);
scoreDisplayMode.BindValueChanged(_ => updateScore());
@@ -298,7 +298,7 @@ namespace osu.Game.Screens.SelectV2
if (s.OnlineID == userScore?.OnlineID)
highlightType = BeatmapLeaderboardScore.HighlightType.Own;
else if (api.Friends.Any(r => r.TargetID == s.UserID) && Scope.Value != BeatmapLeaderboardScope.Friend)
else if (api.LocalUserState.Friends.Any(r => r.TargetID == s.UserID) && Scope.Value != BeatmapLeaderboardScope.Friend)
highlightType = BeatmapLeaderboardScore.HighlightType.Friend;
return new BeatmapLeaderboardScore(s)
+1 -1
View File
@@ -41,7 +41,7 @@ namespace osu.Game.Users
req.Success += () =>
{
api.UpdateLocalBlocks();
api.LocalUserState.UpdateBlocks();
};
req.Failure += e =>
+1 -1
View File
@@ -186,7 +186,7 @@ namespace osu.Game.Users
bool isUserOnline() => metadataClient?.GetPresence(User.OnlineID) != null;
bool canInviteUser() => isUserOnline() && multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true;
bool isUserBlocked() => api.Blocks.Any(b => b.TargetID == User.OnlineID);
bool isUserBlocked() => api.LocalUserState.Blocks.Any(b => b.TargetID == User.OnlineID);
}
}