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:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Users
|
||||
|
||||
req.Success += () =>
|
||||
{
|
||||
api.UpdateLocalBlocks();
|
||||
api.LocalUserState.UpdateBlocks();
|
||||
};
|
||||
|
||||
req.Failure += e =>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user