From 20108e3b74084692b34643d4e61124b079c0aa44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Jan 2025 23:44:14 +0900 Subject: [PATCH 01/11] Remove Status and Activity bindables from APIUser As for the tests, I'm (ab)using the `IsOnline` state for the time being to restore functionality. --- osu.Desktop/DiscordRichPresence.cs | 14 ++++------- .../Visual/Menus/TestSceneLoginOverlay.cs | 2 +- .../Online/TestSceneUserClickableAvatar.cs | 5 +--- .../Visual/Online/TestSceneUserPanel.cs | 2 +- osu.Game/Online/API/APIAccess.cs | 21 ++++------------- osu.Game/Online/API/DummyAPIAccess.cs | 15 ++++-------- osu.Game/Online/API/IAPIProvider.cs | 7 +++++- .../Online/API/Requests/Responses/APIUser.cs | 5 ---- .../Online/Metadata/OnlineMetadataClient.cs | 17 +++++++------- .../Dashboard/CurrentlyOnlineDisplay.cs | 23 +++++++++---------- osu.Game/Overlays/Login/LoginPanel.cs | 19 ++++----------- 11 files changed, 46 insertions(+), 84 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 32a8ba51a3..94804ad1cc 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -54,8 +54,8 @@ namespace osu.Desktop [Resolved] private OsuConfigManager config { get; set; } = null!; - private readonly IBindable status = new Bindable(); - private readonly IBindable activity = new Bindable(); + private readonly IBindable status = new Bindable(); + private readonly IBindable activity = new Bindable(); private readonly Bindable privacyMode = new Bindable(); private readonly RichPresence presence = new RichPresence @@ -108,14 +108,8 @@ namespace osu.Desktop config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); user = api.LocalUser.GetBoundCopy(); - user.BindValueChanged(u => - { - status.UnbindBindings(); - status.BindTo(u.NewValue.Status); - - activity.UnbindBindings(); - activity.BindTo(u.NewValue.Activity); - }, true); + status.BindTo(api.Status); + activity.BindTo(api.Activity); ruleset.BindValueChanged(_ => schedulePresenceUpdate()); status.BindValueChanged(_ => schedulePresenceUpdate()); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 609bc6e166..5c12e0c102 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("clear handler", () => dummyAPI.HandleRequest = null); assertDropdownState(UserAction.Online); - AddStep("change user state", () => dummyAPI.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb); + AddStep("change user state", () => dummyAPI.Status.Value = UserStatus.DoNotDisturb); assertDropdownState(UserAction.DoNotDisturb); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 4539eae25f..fce888094d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -62,10 +62,7 @@ namespace osu.Game.Tests.Visual.Online CountryCode = countryCode, CoverUrl = cover, Colour = color ?? "000000", - Status = - { - Value = UserStatus.Online - }, + IsOnline = true }; return new ClickableAvatar(user, showPanel) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 3f1d961588..4c2e47d336 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, CountryCode = CountryCode.JP, CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", - Status = { Value = UserStatus.Online } + IsOnline = true }) { Width = 300 }, boundPanel1 = new UserGridPanel(new APIUser { diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 00fe3bb005..4f8c5dcb22 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -60,6 +60,7 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; + public Bindable Status { get; } = new Bindable(UserStatus.Online); public IBindable Activity => activity; public INotificationsClient NotificationsClient { get; } @@ -73,7 +74,6 @@ namespace osu.Game.Online.API private Bindable activity { get; } = new Bindable(); private Bindable configStatus { get; } = new Bindable(); - private Bindable localUserStatus { get; } = new Bindable(); protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); @@ -121,17 +121,6 @@ namespace osu.Game.Online.API state.Value = APIState.Connecting; } - localUser.BindValueChanged(u => - { - u.OldValue?.Activity.UnbindFrom(activity); - u.NewValue.Activity.BindTo(activity); - - u.OldValue?.Status.UnbindFrom(localUserStatus); - u.NewValue.Status.BindTo(localUserStatus); - }, true); - - localUserStatus.BindTo(configStatus); - var thread = new Thread(run) { Name = "APIAccess", @@ -342,9 +331,8 @@ namespace osu.Game.Online.API { Debug.Assert(ThreadSafety.IsUpdateThread); - me.Status.Value = configStatus.Value ?? UserStatus.Online; - localUser.Value = me; + Status.Value = configStatus.Value ?? UserStatus.Online; state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; failureCount = 0; @@ -381,9 +369,10 @@ namespace osu.Game.Online.API localUser.Value = new APIUser { - Username = ProvidedUsername, - Status = { Value = configStatus.Value ?? UserStatus.Online } + Username = ProvidedUsername }; + + Status.Value = configStatus.Value ?? UserStatus.Online; } public void Perform(APIRequest request) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 5d63c04925..b338f4e8cb 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -28,7 +28,9 @@ namespace osu.Game.Online.API public BindableList Friends { get; } = new BindableList(); - public Bindable Activity { get; } = new Bindable(); + public Bindable Status { get; } = new Bindable(UserStatus.Online); + + public Bindable Activity { get; } = new Bindable(); public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient(); INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient; @@ -69,15 +71,6 @@ namespace osu.Game.Online.API /// public IBindable State => state; - public DummyAPIAccess() - { - LocalUser.BindValueChanged(u => - { - u.OldValue?.Activity.UnbindFrom(Activity); - u.NewValue.Activity.BindTo(Activity); - }, true); - } - public virtual void Queue(APIRequest request) { request.AttachAPI(this); @@ -204,7 +197,7 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; - IBindable IAPIProvider.Activity => Activity; + IBindable IAPIProvider.Activity => Activity; /// /// Skip 2FA requirement for next login. diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 1c4b2da742..cc065a659a 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -24,10 +24,15 @@ namespace osu.Game.Online.API /// IBindableList Friends { get; } + /// + /// The current user's status. + /// + Bindable Status { get; } + /// /// The current user's activity. /// - IBindable Activity { get; } + IBindable Activity { get; } /// /// The language supplied by this provider to API requests. diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index a829484506..30fceab852 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; -using osu.Framework.Bindables; using osu.Game.Extensions; using osu.Game.Users; @@ -56,10 +55,6 @@ namespace osu.Game.Online.API.Requests.Responses set => countryCodeString = value.ToString(); } - public readonly Bindable Status = new Bindable(); - - public readonly Bindable Activity = new Bindable(); - [JsonProperty(@"profile_colour")] public string Colour; diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index a8a14b1c78..b3204a7cd1 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -37,8 +37,9 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; private Bindable lastQueueId = null!; private IBindable localUser = null!; + + private IBindable userStatus = null!; private IBindable userActivity = null!; - private IBindable? userStatus; private HubConnection? connection => connector?.CurrentConnection; @@ -75,22 +76,20 @@ namespace osu.Game.Online.Metadata lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); localUser = api.LocalUser.GetBoundCopy(); + userStatus = api.Status.GetBoundCopy(); userActivity = api.Activity.GetBoundCopy()!; } protected override void LoadComplete() { base.LoadComplete(); - localUser.BindValueChanged(_ => + + userStatus.BindValueChanged(status => { if (localUser.Value is not GuestUser) - { - userStatus = localUser.Value.Status.GetBoundCopy(); - userStatus.BindValueChanged(status => UpdateStatus(status.NewValue), true); - } - else - userStatus = null; + UpdateStatus(status.NewValue); }, true); + userActivity.BindValueChanged(activity => { if (localUser.Value is not GuestUser) @@ -117,7 +116,7 @@ namespace osu.Game.Online.Metadata if (localUser.Value is not GuestUser) { UpdateActivity(userActivity.Value); - UpdateStatus(userStatus?.Value); + UpdateStatus(userStatus.Value); } if (lastQueueId.Value >= 0) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index ee277ff538..2ca548fdf5 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -140,15 +140,11 @@ namespace osu.Game.Overlays.Dashboard Schedule(() => { - // explicitly refetch the user's status. - // things may have changed in between the time of scheduling and the time of actual execution. - if (onlineUsers.TryGetValue(userId, out var updatedStatus)) + userFlow.Add(userPanels[userId] = createUserPanel(user).With(p => { - user.Activity.Value = updatedStatus.Activity; - user.Status.Value = updatedStatus.Status; - } - - userFlow.Add(userPanels[userId] = createUserPanel(user)); + p.Status.Value = onlineUsers.GetValueOrDefault(userId).Status; + p.Activity.Value = onlineUsers.GetValueOrDefault(userId).Activity; + })); }); }); } @@ -162,8 +158,8 @@ namespace osu.Game.Overlays.Dashboard { if (userPanels.TryGetValue(kvp.Key, out var panel)) { - panel.User.Activity.Value = kvp.Value.Activity; - panel.User.Status.Value = kvp.Value.Status; + panel.Activity.Value = kvp.Value.Activity; + panel.Status.Value = kvp.Value.Status; } } @@ -223,6 +219,9 @@ namespace osu.Game.Overlays.Dashboard { public readonly APIUser User; + public readonly Bindable Status = new Bindable(); + public readonly Bindable Activity = new Bindable(); + public BindableBool CanSpectate { get; } = new BindableBool(); public IEnumerable FilterTerms { get; } @@ -271,8 +270,8 @@ namespace osu.Game.Overlays.Dashboard Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, // this is SHOCKING - Activity = { BindTarget = User.Activity }, - Status = { BindTarget = User.Status }, + Activity = { BindTarget = Activity }, + Status = { BindTarget = Status }, }, new PurpleRoundedButton { diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 84bd0c36b9..b947731f8b 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Settings; using osu.Game.Users; using osuTK; @@ -38,9 +37,7 @@ namespace osu.Game.Overlays.Login /// public Action? RequestHide; - private IBindable user = null!; - private readonly Bindable status = new Bindable(); - + private readonly Bindable status = new Bindable(); private readonly IBindable apiState = new Bindable(); [Resolved] @@ -71,13 +68,7 @@ namespace osu.Game.Overlays.Login apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - user = api.LocalUser.GetBoundCopy(); - user.BindValueChanged(u => - { - status.UnbindBindings(); - status.BindTo(u.NewValue.Status); - }, true); - + status.BindTo(api.Status); status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); } @@ -163,17 +154,17 @@ namespace osu.Game.Overlays.Login switch (action.NewValue) { case UserAction.Online: - api.LocalUser.Value.Status.Value = UserStatus.Online; + status.Value = UserStatus.Online; dropdown.StatusColour = colours.Green; break; case UserAction.DoNotDisturb: - api.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb; + status.Value = UserStatus.DoNotDisturb; dropdown.StatusColour = colours.Red; break; case UserAction.AppearOffline: - api.LocalUser.Value.Status.Value = UserStatus.Offline; + status.Value = UserStatus.Offline; dropdown.StatusColour = colours.Gray7; break; From b7a9b77efef2590a6f47e013165c95c71d837bb3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Jan 2025 00:01:19 +0900 Subject: [PATCH 02/11] Make config the definitive status value --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Online/API/APIAccess.cs | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d4a75334a9..642da16d2d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -211,7 +211,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LastProcessedMetadataId, -1); SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); - SetDefault(OsuSetting.UserOnlineStatus, null); + SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online); SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowBreaks, true); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4f8c5dcb22..a4ac577a02 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -73,8 +73,6 @@ namespace osu.Game.Online.API private Bindable activity { get; } = new Bindable(); - private Bindable configStatus { get; } = new Bindable(); - protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -110,7 +108,7 @@ namespace osu.Game.Online.API authentication.TokenString = config.Get(OsuSetting.Token); authentication.Token.ValueChanged += onTokenChanged; - config.BindWith(OsuSetting.UserOnlineStatus, configStatus); + config.BindWith(OsuSetting.UserOnlineStatus, Status); if (HasLogin) { @@ -332,8 +330,6 @@ namespace osu.Game.Online.API Debug.Assert(ThreadSafety.IsUpdateThread); localUser.Value = me; - Status.Value = configStatus.Value ?? UserStatus.Online; - state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; failureCount = 0; }; @@ -371,8 +367,6 @@ namespace osu.Game.Online.API { Username = ProvidedUsername }; - - Status.Value = configStatus.Value ?? UserStatus.Online; } public void Perform(APIRequest request) @@ -597,7 +591,7 @@ namespace osu.Game.Online.API password = null; SecondFactorCode = null; authentication.Clear(); - configStatus.Value = UserStatus.Online; + Status.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(() => From 7ca3a6fc26f78c639ddefb725c25f40442c94dc6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Jan 2025 17:48:22 +0900 Subject: [PATCH 03/11] Clear Discord presence when logged out --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 94804ad1cc..6c7e7d393f 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -145,7 +145,7 @@ namespace osu.Desktop if (!client.IsInitialized) return; - if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) + if (!api.IsLoggedIn || status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) { client.ClearPresence(); return; From b54d95926329c0af71df64458196ec4339b66147 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Jan 2025 17:05:18 +0900 Subject: [PATCH 04/11] Expose as IBindable from IAPIProvider, writes via config --- .../Visual/Menus/TestSceneLoginOverlay.cs | 27 ++++++++++--------- osu.Game/Configuration/OsuConfigManager.cs | 5 ++++ osu.Game/Online/API/APIAccess.cs | 10 ++++--- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- osu.Game/Online/API/IAPIProvider.cs | 6 ++--- .../Online/Metadata/OnlineMetadataClient.cs | 1 - osu.Game/Overlays/Login/LoginPanel.cs | 20 ++++++++------ 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 5c12e0c102..3c97b291ee 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -29,9 +29,7 @@ namespace osu.Game.Tests.Visual.Menus private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private LoginOverlay loginOverlay = null!; - - [Resolved] - private OsuConfigManager configManager { get; set; } = null!; + private OsuConfigManager localConfig = null!; [Cached(typeof(LocalUserStatisticsProvider))] private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider(); @@ -39,6 +37,8 @@ namespace osu.Game.Tests.Visual.Menus [BackgroundDependencyLoader] private void load() { + Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); + Child = loginOverlay = new LoginOverlay { Anchor = Anchor.Centre, @@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.Menus [SetUpSteps] public void SetUpSteps() { + AddStep("reset online state", () => localConfig.SetValue(OsuSetting.UserOnlineStatus, UserStatus.Online)); AddStep("show login overlay", () => loginOverlay.Show()); } @@ -89,7 +90,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("clear handler", () => dummyAPI.HandleRequest = null); assertDropdownState(UserAction.Online); - AddStep("change user state", () => dummyAPI.Status.Value = UserStatus.DoNotDisturb); + AddStep("change user state", () => localConfig.SetValue(OsuSetting.UserOnlineStatus, UserStatus.DoNotDisturb)); assertDropdownState(UserAction.DoNotDisturb); } @@ -188,31 +189,31 @@ namespace osu.Game.Tests.Visual.Menus public void TestUncheckingRememberUsernameClearsIt() { AddStep("logout", () => API.Logout()); - AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user")); - AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); + AddStep("set username", () => localConfig.SetValue(OsuSetting.Username, "test_user")); + AddStep("set remember password", () => localConfig.SetValue(OsuSetting.SavePassword, true)); AddStep("uncheck remember username", () => { InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("remember username off", () => configManager.Get(OsuSetting.SaveUsername), () => Is.False); - AddAssert("remember password off", () => configManager.Get(OsuSetting.SavePassword), () => Is.False); - AddAssert("username cleared", () => configManager.Get(OsuSetting.Username), () => Is.Empty); + AddAssert("remember username off", () => localConfig.Get(OsuSetting.SaveUsername), () => Is.False); + AddAssert("remember password off", () => localConfig.Get(OsuSetting.SavePassword), () => Is.False); + AddAssert("username cleared", () => localConfig.Get(OsuSetting.Username), () => Is.Empty); } [Test] public void TestUncheckingRememberPasswordClearsToken() { AddStep("logout", () => API.Logout()); - AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token")); - AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); + AddStep("set token", () => localConfig.SetValue(OsuSetting.Token, "test_token")); + AddStep("set remember password", () => localConfig.SetValue(OsuSetting.SavePassword, true)); AddStep("uncheck remember token", () => { InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().Last()); InputManager.Click(MouseButton.Left); }); - AddAssert("remember password off", () => configManager.Get(OsuSetting.SavePassword), () => Is.False); - AddAssert("token cleared", () => configManager.Get(OsuSetting.Token), () => Is.Empty); + AddAssert("remember password off", () => localConfig.Get(OsuSetting.SavePassword), () => Is.False); + AddAssert("token cleared", () => localConfig.Get(OsuSetting.Token), () => Is.Empty); } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 642da16d2d..d4f5b2af76 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -443,7 +443,12 @@ namespace osu.Game.Configuration EditorShowSpeedChanges, TouchDisableGameplayTaps, ModSelectTextSearchStartsActive, + + /// + /// The status for the current user to broadcast to other players. + /// UserOnlineStatus, + MultiplayerRoomFilter, HideCountryFlags, EditorTimelineShowTimingChanges, diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a4ac577a02..dcb8a193bc 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -60,7 +60,7 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; - public Bindable Status { get; } = new Bindable(UserStatus.Online); + public IBindable Status => configStatus; public IBindable Activity => activity; public INotificationsClient NotificationsClient { get; } @@ -75,8 +75,8 @@ namespace osu.Game.Online.API protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); + private readonly Bindable configStatus = new Bindable(); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); - private readonly Logger log; public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash) @@ -108,7 +108,7 @@ namespace osu.Game.Online.API authentication.TokenString = config.Get(OsuSetting.Token); authentication.Token.ValueChanged += onTokenChanged; - config.BindWith(OsuSetting.UserOnlineStatus, Status); + config.BindWith(OsuSetting.UserOnlineStatus, configStatus); if (HasLogin) { @@ -591,7 +591,9 @@ namespace osu.Game.Online.API password = null; SecondFactorCode = null; authentication.Clear(); - Status.Value = UserStatus.Online; + + // 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(() => diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index b338f4e8cb..4cd3c02414 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.API public BindableList Friends { get; } = new BindableList(); - public Bindable Status { get; } = new Bindable(UserStatus.Online); + public IBindable Status { get; } = new Bindable(UserStatus.Online); public Bindable Activity { get; } = new Bindable(); diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index cc065a659a..9ac7343885 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -25,12 +25,12 @@ namespace osu.Game.Online.API IBindableList Friends { get; } /// - /// The current user's status. + /// The status for the current user that's broadcast to other players. /// - Bindable Status { get; } + IBindable Status { get; } /// - /// The current user's activity. + /// The activity for the current user that's broadcast to other players. /// IBindable Activity { get; } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index b3204a7cd1..101307636a 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -37,7 +37,6 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; private Bindable lastQueueId = null!; private IBindable localUser = null!; - private IBindable userStatus = null!; private IBindable userActivity = null!; diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index b947731f8b..6d74fc442e 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -37,12 +38,15 @@ namespace osu.Game.Overlays.Login /// public Action? RequestHide; - private readonly Bindable status = new Bindable(); private readonly IBindable apiState = new Bindable(); + private readonly Bindable configUserStatus = new Bindable(); [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private OsuConfigManager config { get; set; } = null!; + public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; public bool Bounding @@ -65,11 +69,11 @@ namespace osu.Game.Overlays.Login { base.LoadComplete(); + config.BindWith(OsuSetting.UserOnlineStatus, configUserStatus); + configUserStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); + apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - - status.BindTo(api.Status); - status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true); } private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => @@ -148,23 +152,23 @@ namespace osu.Game.Overlays.Login }, }; - updateDropdownCurrent(status.Value); + updateDropdownCurrent(configUserStatus.Value); dropdown.Current.BindValueChanged(action => { switch (action.NewValue) { case UserAction.Online: - status.Value = UserStatus.Online; + configUserStatus.Value = UserStatus.Online; dropdown.StatusColour = colours.Green; break; case UserAction.DoNotDisturb: - status.Value = UserStatus.DoNotDisturb; + configUserStatus.Value = UserStatus.DoNotDisturb; dropdown.StatusColour = colours.Red; break; case UserAction.AppearOffline: - status.Value = UserStatus.Offline; + configUserStatus.Value = UserStatus.Offline; dropdown.StatusColour = colours.Gray7; break; From c1f0c47586a3816936a5148732ccd4545eaf0a9b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Jan 2025 17:06:54 +0900 Subject: [PATCH 05/11] Allow setting of DummyAPIAccess status --- osu.Game/Online/API/DummyAPIAccess.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4cd3c02414..3fef2b59cf 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -28,7 +28,7 @@ namespace osu.Game.Online.API public BindableList Friends { get; } = new BindableList(); - public IBindable Status { get; } = new Bindable(UserStatus.Online); + public Bindable Status { get; } = new Bindable(UserStatus.Online); public Bindable Activity { get; } = new Bindable(); @@ -197,6 +197,7 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; + IBindable IAPIProvider.Status => Status; IBindable IAPIProvider.Activity => Activity; /// From 840072688749f6c24f3aab3926d9eeed22b36861 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Jan 2025 19:33:38 +0900 Subject: [PATCH 06/11] Move bindables to OsuConfigManager & SessionStatics --- osu.Desktop/DiscordRichPresence.cs | 35 +++++++++---------- .../Online/TestSceneNowPlayingCommand.cs | 20 +++++++---- osu.Game/Configuration/SessionStatics.cs | 4 +++ osu.Game/Online/API/APIAccess.cs | 4 --- osu.Game/Online/API/DummyAPIAccess.cs | 7 ---- osu.Game/Online/API/IAPIProvider.cs | 11 ------ osu.Game/Online/Chat/NowPlayingCommand.cs | 14 ++++++-- .../Online/Metadata/OnlineMetadataClient.cs | 21 +++++++---- osu.Game/OsuGame.cs | 8 +++-- osu.Game/Screens/IOsuScreen.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 2 +- 11 files changed, 67 insertions(+), 61 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 6c7e7d393f..7dd9250ab6 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -51,12 +51,9 @@ namespace osu.Desktop [Resolved] private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!; - [Resolved] - private OsuConfigManager config { get; set; } = null!; - - private readonly IBindable status = new Bindable(); - private readonly IBindable activity = new Bindable(); - private readonly Bindable privacyMode = new Bindable(); + private IBindable privacyMode = null!; + private IBindable userStatus = null!; + private IBindable userActivity = null!; private readonly RichPresence presence = new RichPresence { @@ -71,8 +68,12 @@ namespace osu.Desktop private IBindable? user; [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config, SessionStatics session) { + privacyMode = config.GetBindable(OsuSetting.DiscordRichPresence); + userStatus = config.GetBindable(OsuSetting.UserOnlineStatus); + userActivity = session.GetBindable(Static.UserOnlineActivity); + client = new DiscordRpcClient(client_id) { // SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation @@ -105,15 +106,11 @@ namespace osu.Desktop { base.LoadComplete(); - config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); - user = api.LocalUser.GetBoundCopy(); - status.BindTo(api.Status); - activity.BindTo(api.Activity); ruleset.BindValueChanged(_ => schedulePresenceUpdate()); - status.BindValueChanged(_ => schedulePresenceUpdate()); - activity.BindValueChanged(_ => schedulePresenceUpdate()); + userStatus.BindValueChanged(_ => schedulePresenceUpdate()); + userActivity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); multiplayerClient.RoomUpdated += onRoomUpdated; @@ -145,13 +142,13 @@ namespace osu.Desktop if (!client.IsInitialized) return; - if (!api.IsLoggedIn || status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) + if (!api.IsLoggedIn || userStatus.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) { client.ClearPresence(); return; } - bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; + bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || userStatus.Value == UserStatus.DoNotDisturb; updatePresence(hideIdentifiableInformation); client.SetPresence(presence); @@ -164,12 +161,12 @@ namespace osu.Desktop return; // user activity - if (activity.Value != null) + if (userActivity.Value != null) { - presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation)); - presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); + presence.State = clampLength(userActivity.Value.GetStatus(hideIdentifiableInformation)); + presence.Details = clampLength(userActivity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); - if (activity.Value.GetBeatmapID(hideIdentifiableInformation) is int beatmapId && beatmapId > 0) + if (userActivity.Value.GetBeatmapID(hideIdentifiableInformation) is int beatmapId && beatmapId > 0) { presence.Buttons = new[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 1e9b0317fb..428554f761 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Online.API; +using osu.Game.Configuration; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; @@ -23,17 +23,23 @@ namespace osu.Game.Tests.Visual.Online [Cached(typeof(IChannelPostTarget))] private PostTarget postTarget { get; set; } - private DummyAPIAccess api => (DummyAPIAccess)API; + private SessionStatics session = null!; public TestSceneNowPlayingCommand() { Add(postTarget = new PostTarget()); } + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(session = new SessionStatics()); + } + [Test] public void TestGenericActivity() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(new Room())); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -43,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEditActivity() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo())); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.EditingBeatmap(new BeatmapInfo()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -53,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayActivity() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -64,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online [TestCase(false)] public void TestLinkPresence(bool hasOnlineId) { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(new Room())); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { @@ -82,7 +88,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestModPresence() { - AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod() }); diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index c55a597c32..bdfb0217ad 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Configuration { @@ -30,6 +31,7 @@ namespace osu.Game.Configuration SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile); SetDefault(Static.LastLocalUserScore, null); SetDefault(Static.LastAppliedOffsetScore, null); + SetDefault(Static.UserOnlineActivity, null); } /// @@ -92,5 +94,7 @@ namespace osu.Game.Configuration /// This is reset when a new challenge is up. /// DailyChallengeIntroPlayed, + + UserOnlineActivity, } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index dcb8a193bc..f7fbacf76c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -60,8 +60,6 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; - public IBindable Status => configStatus; - public IBindable Activity => activity; public INotificationsClient NotificationsClient { get; } @@ -71,8 +69,6 @@ namespace osu.Game.Online.API private BindableList friends { get; } = new BindableList(); - private Bindable activity { get; } = new Bindable(); - protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly Bindable configStatus = new Bindable(); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 3fef2b59cf..48c08afb8c 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Online.Notifications.WebSocket; using osu.Game.Tests; -using osu.Game.Users; namespace osu.Game.Online.API { @@ -28,10 +27,6 @@ namespace osu.Game.Online.API public BindableList Friends { get; } = new BindableList(); - public Bindable Status { get; } = new Bindable(UserStatus.Online); - - public Bindable Activity { get; } = new Bindable(); - public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient(); INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient; @@ -197,8 +192,6 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; - IBindable IAPIProvider.Status => Status; - IBindable IAPIProvider.Activity => Activity; /// /// Skip 2FA requirement for next login. diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 9ac7343885..3b6763d736 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -8,7 +8,6 @@ using osu.Game.Localisation; 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 { @@ -24,16 +23,6 @@ namespace osu.Game.Online.API /// IBindableList Friends { get; } - /// - /// The status for the current user that's broadcast to other players. - /// - IBindable Status { get; } - - /// - /// The activity for the current user that's broadcast to other players. - /// - IBindable Activity { get; } - /// /// The language supplied by this provider to API requests. /// diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 0e6f6f0bf6..db44017a1b 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -33,6 +34,7 @@ namespace osu.Game.Online.Chat private IBindable currentRuleset { get; set; } = null!; private readonly Channel? target; + private IBindable userActivity = null!; /// /// Creates a new to post the currently-playing beatmap to a parenting . @@ -43,6 +45,12 @@ namespace osu.Game.Online.Chat this.target = target; } + [BackgroundDependencyLoader] + private void load(SessionStatics session) + { + userActivity = session.GetBindable(Static.UserOnlineActivity); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -52,7 +60,7 @@ namespace osu.Game.Online.Chat int beatmapOnlineID; string beatmapDisplayTitle; - switch (api.Activity.Value) + switch (userActivity.Value) { case UserActivity.InGame game: verb = "playing"; @@ -92,14 +100,14 @@ namespace osu.Game.Online.Chat string getRulesetPart() { - if (api.Activity.Value is not UserActivity.InGame) return string.Empty; + if (userActivity.Value is not UserActivity.InGame) return string.Empty; return $"<{currentRuleset.Value.Name}>"; } string getModPart() { - if (api.Activity.Value is not UserActivity.InGame) return string.Empty; + if (userActivity.Value is not UserActivity.InGame) return string.Empty; if (selectedMods.Value.Count == 0) { diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 101307636a..01d7a564fa 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -34,6 +34,9 @@ namespace osu.Game.Online.Metadata private readonly string endpoint; + [Resolved] + private IAPIProvider api { get; set; } = null!; + private IHubClientConnector? connector; private Bindable lastQueueId = null!; private IBindable localUser = null!; @@ -48,7 +51,7 @@ namespace osu.Game.Online.Metadata } [BackgroundDependencyLoader] - private void load(IAPIProvider api, OsuConfigManager config) + private void load(OsuConfigManager config, SessionStatics session) { // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. @@ -72,11 +75,10 @@ namespace osu.Game.Online.Metadata IsConnected.BindValueChanged(isConnectedChanged, true); } - lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); - localUser = api.LocalUser.GetBoundCopy(); - userStatus = api.Status.GetBoundCopy(); - userActivity = api.Activity.GetBoundCopy()!; + lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); + userStatus = config.GetBindable(OsuSetting.UserOnlineStatus); + userActivity = session.GetBindable(Static.UserOnlineActivity); } protected override void LoadComplete() @@ -240,7 +242,14 @@ namespace osu.Game.Online.Metadata throw new OperationCanceledException(); // must be scheduled before any remote calls to avoid mis-ordering. - Schedule(() => userStates.Clear()); + Schedule(() => + { + bool hadLocalUserState = userStates.TryGetValue(api.LocalUser.Value.OnlineID, out var presence); + userStates.Clear(); + if (hadLocalUserState) + userStates[api.LocalUser.Value.OnlineID] = presence; + }); + Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 859991496d..40d13ae0b7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -211,6 +211,8 @@ namespace osu.Game private Bindable uiScale; + private Bindable configUserActivity; + private Bindable configSkin; private readonly string[] args; @@ -391,6 +393,8 @@ namespace osu.Game Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName; + configUserActivity = SessionStatics.GetBindable(Static.UserOnlineActivity); + configSkin = LocalConfig.GetBindable(OsuSetting.Skin); // Transfer skin from config to realm instance once on startup. @@ -1588,14 +1592,14 @@ namespace osu.Game { backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); - API.Activity.UnbindFrom(currentOsuScreen.Activity); + configUserActivity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); - API.Activity.BindTo(newOsuScreen.Activity); + configUserActivity.BindTo(newOsuScreen.Activity); GlobalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = newOsuScreen.HideMenuCursorOnNonMouseInput; diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 9e474ed0c6..69bde877c7 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens /// /// The current for this screen. /// - IBindable Activity { get; } + Bindable Activity { get; } /// /// The amount of parallax to be applied while this screen is displayed. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index ab66241a77..f5325b3928 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens /// protected readonly Bindable Activity = new Bindable(); - IBindable IOsuScreen.Activity => Activity; + Bindable IOsuScreen.Activity => Activity; /// /// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children). From ae7e4bef86d68dfb6e3db8f406f97c152e314cff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 17 Jan 2025 15:42:19 +0900 Subject: [PATCH 07/11] Fix tests --- .../Visual/Online/TestSceneNowPlayingCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 428554f761..56d03d4c7f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestGenericActivity() { - AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEditActivity() { - AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.EditingBeatmap(new BeatmapInfo()))); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.EditingBeatmap(new BeatmapInfo()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayActivity() { - AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online [TestCase(false)] public void TestLinkPresence(bool hasOnlineId) { - AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InLobby(new Room()))); AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestModPresence() { - AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); + AddStep("Set activity", () => session.SetValue(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo))); AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod() }); From a51938f4e97c3d09673dc677bc368d17b351dfaf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 17 Jan 2025 15:59:25 +0900 Subject: [PATCH 08/11] Separate the local user state --- osu.Game/Online/Metadata/MetadataClient.cs | 5 ++++ .../Online/Metadata/OnlineMetadataClient.cs | 27 ++++++++++++------- .../Visual/Metadata/TestMetadataClient.cs | 19 ++++++++++--- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 6578f70f74..507f43467c 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -37,6 +37,11 @@ namespace osu.Game.Online.Metadata /// public abstract IBindable IsWatchingUserPresence { get; } + /// + /// The information about the current user. + /// + public abstract UserPresence LocalUserState { get; } + /// /// Dictionary keyed by user ID containing all of the information about currently online users received from the server. /// diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 01d7a564fa..04abca1e9b 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -23,6 +23,9 @@ namespace osu.Game.Online.Metadata public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); + public override UserPresence LocalUserState => localUserState; + private UserPresence localUserState; + public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); @@ -110,6 +113,7 @@ namespace osu.Game.Online.Metadata userStates.Clear(); friendStates.Clear(); dailyChallengeInfo.Value = null; + localUserState = default; }); return; } @@ -202,9 +206,19 @@ namespace osu.Game.Online.Metadata Schedule(() => { if (presence?.Status != null) - userStates[userId] = presence.Value; + { + if (userId == api.LocalUser.Value.OnlineID) + localUserState = presence.Value; + else + userStates[userId] = presence.Value; + } else - userStates.Remove(userId); + { + if (userId == api.LocalUser.Value.OnlineID) + localUserState = default; + else + userStates.Remove(userId); + } }); return Task.CompletedTask; @@ -242,14 +256,7 @@ namespace osu.Game.Online.Metadata throw new OperationCanceledException(); // must be scheduled before any remote calls to avoid mis-ordering. - Schedule(() => - { - bool hadLocalUserState = userStates.TryGetValue(api.LocalUser.Value.OnlineID, out var presence); - userStates.Clear(); - if (hadLocalUserState) - userStates[api.LocalUser.Value.OnlineID] = presence; - }); - + Schedule(() => userStates.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 36f79a5adc..d32d49b55e 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -19,6 +19,9 @@ namespace osu.Game.Tests.Visual.Metadata public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); + public override UserPresence LocalUserState => localUserState; + private UserPresence localUserState; + public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); @@ -71,10 +74,20 @@ namespace osu.Game.Tests.Visual.Metadata { if (isWatchingUserPresence.Value) { - if (presence.HasValue) - userStates[userId] = presence.Value; + if (presence?.Status != null) + { + if (userId == api.LocalUser.Value.OnlineID) + localUserState = presence.Value; + else + userStates[userId] = presence.Value; + } else - userStates.Remove(userId); + { + if (userId == api.LocalUser.Value.OnlineID) + localUserState = default; + else + userStates.Remove(userId); + } } return Task.CompletedTask; From 3bb4b0c2b8a84c5bf3330a84422e6f3c077b346f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jan 2025 16:25:48 +0900 Subject: [PATCH 09/11] Rename fields from `State` to `Presence` when presence is involved --- osu.Game/Online/FriendPresenceNotifier.cs | 10 +++--- osu.Game/Online/Metadata/MetadataClient.cs | 6 ++-- .../Online/Metadata/OnlineMetadataClient.cs | 32 +++++++++---------- .../Dashboard/CurrentlyOnlineDisplay.cs | 2 +- .../Visual/Metadata/TestMetadataClient.cs | 32 +++++++++---------- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game/Online/FriendPresenceNotifier.cs b/osu.Game/Online/FriendPresenceNotifier.cs index 330e0a908f..dd141b756b 100644 --- a/osu.Game/Online/FriendPresenceNotifier.cs +++ b/osu.Game/Online/FriendPresenceNotifier.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online private readonly Bindable notifyOnFriendPresenceChange = new BindableBool(); private readonly IBindableList friends = new BindableList(); - private readonly IBindableDictionary friendStates = new BindableDictionary(); + private readonly IBindableDictionary friendPresences = new BindableDictionary(); private readonly HashSet onlineAlertQueue = new HashSet(); private readonly HashSet offlineAlertQueue = new HashSet(); @@ -63,8 +63,8 @@ namespace osu.Game.Online friends.BindTo(api.Friends); friends.BindCollectionChanged(onFriendsChanged, true); - friendStates.BindTo(metadataClient.FriendStates); - friendStates.BindCollectionChanged(onFriendStatesChanged, true); + friendPresences.BindTo(metadataClient.FriendPresences); + friendPresences.BindCollectionChanged(onFriendPresenceChanged, true); } protected override void Update() @@ -85,7 +85,7 @@ namespace osu.Game.Online if (friend.TargetUser is not APIUser user) continue; - if (friendStates.TryGetValue(friend.TargetID, out _)) + if (friendPresences.TryGetValue(friend.TargetID, out _)) markUserOnline(user); } @@ -105,7 +105,7 @@ namespace osu.Game.Online } } - private void onFriendStatesChanged(object? sender, NotifyDictionaryChangedEventArgs e) + private void onFriendPresenceChanged(object? sender, NotifyDictionaryChangedEventArgs e) { switch (e.Action) { diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 507f43467c..3c0b47ad3d 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -40,17 +40,17 @@ namespace osu.Game.Online.Metadata /// /// The information about the current user. /// - public abstract UserPresence LocalUserState { get; } + public abstract UserPresence LocalUserPresence { get; } /// /// Dictionary keyed by user ID containing all of the information about currently online users received from the server. /// - public abstract IBindableDictionary UserStates { get; } + public abstract IBindableDictionary UserPresences { get; } /// /// Dictionary keyed by user ID containing all of the information about currently online friends received from the server. /// - public abstract IBindableDictionary FriendStates { get; } + public abstract IBindableDictionary FriendPresences { get; } /// public abstract Task UpdateActivity(UserActivity? activity); diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 04abca1e9b..5aeeb04d11 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -23,14 +23,14 @@ namespace osu.Game.Online.Metadata public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); - public override UserPresence LocalUserState => localUserState; - private UserPresence localUserState; + public override UserPresence LocalUserPresence => localUserPresence; + private UserPresence localUserPresence; - public override IBindableDictionary UserStates => userStates; - private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindableDictionary UserPresences => userPresences; + private readonly BindableDictionary userPresences = new BindableDictionary(); - public override IBindableDictionary FriendStates => friendStates; - private readonly BindableDictionary friendStates = new BindableDictionary(); + public override IBindableDictionary FriendPresences => friendPresences; + private readonly BindableDictionary friendPresences = new BindableDictionary(); public override IBindable DailyChallengeInfo => dailyChallengeInfo; private readonly Bindable dailyChallengeInfo = new Bindable(); @@ -110,10 +110,10 @@ namespace osu.Game.Online.Metadata Schedule(() => { isWatchingUserPresence.Value = false; - userStates.Clear(); - friendStates.Clear(); + userPresences.Clear(); + friendPresences.Clear(); dailyChallengeInfo.Value = null; - localUserState = default; + localUserPresence = default; }); return; } @@ -208,16 +208,16 @@ namespace osu.Game.Online.Metadata if (presence?.Status != null) { if (userId == api.LocalUser.Value.OnlineID) - localUserState = presence.Value; + localUserPresence = presence.Value; else - userStates[userId] = presence.Value; + userPresences[userId] = presence.Value; } else { if (userId == api.LocalUser.Value.OnlineID) - localUserState = default; + localUserPresence = default; else - userStates.Remove(userId); + userPresences.Remove(userId); } }); @@ -229,9 +229,9 @@ namespace osu.Game.Online.Metadata Schedule(() => { if (presence?.Status != null) - friendStates[userId] = presence.Value; + friendPresences[userId] = presence.Value; else - friendStates.Remove(userId); + friendPresences.Remove(userId); }); return Task.CompletedTask; @@ -256,7 +256,7 @@ namespace osu.Game.Online.Metadata throw new OperationCanceledException(); // must be scheduled before any remote calls to avoid mis-ordering. - Schedule(() => userStates.Clear()); + Schedule(() => userPresences.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 2ca548fdf5..39023c16f6 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - onlineUsers.BindTo(metadataClient.UserStates); + onlineUsers.BindTo(metadataClient.UserPresences); onlineUsers.BindCollectionChanged(onUserUpdated, true); playingUsers.BindTo(spectatorClient.PlayingUsers); diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index d32d49b55e..7b08108194 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual.Metadata public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); - public override UserPresence LocalUserState => localUserState; - private UserPresence localUserState; + public override UserPresence LocalUserPresence => localUserPresence; + private UserPresence localUserPresence; - public override IBindableDictionary UserStates => userStates; - private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindableDictionary UserPresences => userPresences; + private readonly BindableDictionary userPresences = new BindableDictionary(); - public override IBindableDictionary FriendStates => friendStates; - private readonly BindableDictionary friendStates = new BindableDictionary(); + public override IBindableDictionary FriendPresences => friendPresences; + private readonly BindableDictionary friendPresences = new BindableDictionary(); public override Bindable DailyChallengeInfo => dailyChallengeInfo; private readonly Bindable dailyChallengeInfo = new Bindable(); @@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Metadata { if (isWatchingUserPresence.Value) { - userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); + userPresences.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); localUserPresence = localUserPresence with { Activity = activity }; - userStates[api.LocalUser.Value.Id] = localUserPresence; + userPresences[api.LocalUser.Value.Id] = localUserPresence; } return Task.CompletedTask; @@ -62,9 +62,9 @@ namespace osu.Game.Tests.Visual.Metadata { if (isWatchingUserPresence.Value) { - userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); + userPresences.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); localUserPresence = localUserPresence with { Status = status }; - userStates[api.LocalUser.Value.Id] = localUserPresence; + userPresences[api.LocalUser.Value.Id] = localUserPresence; } return Task.CompletedTask; @@ -77,16 +77,16 @@ namespace osu.Game.Tests.Visual.Metadata if (presence?.Status != null) { if (userId == api.LocalUser.Value.OnlineID) - localUserState = presence.Value; + localUserPresence = presence.Value; else - userStates[userId] = presence.Value; + userPresences[userId] = presence.Value; } else { if (userId == api.LocalUser.Value.OnlineID) - localUserState = default; + localUserPresence = default; else - userStates.Remove(userId); + userPresences.Remove(userId); } } @@ -96,9 +96,9 @@ namespace osu.Game.Tests.Visual.Metadata public override Task FriendPresenceUpdated(int userId, UserPresence? presence) { if (presence.HasValue) - friendStates[userId] = presence.Value; + friendPresences[userId] = presence.Value; else - friendStates.Remove(userId); + friendPresences.Remove(userId); return Task.CompletedTask; } From 311f08b962a3ca2d99bc42f82459a231bbf41fa8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jan 2025 16:29:02 +0900 Subject: [PATCH 10/11] Update `TestMetadataClient` to correctly set local user state in line with changes --- .../Tests/Visual/Metadata/TestMetadataClient.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 7b08108194..d14cbd7743 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -48,11 +48,12 @@ namespace osu.Game.Tests.Visual.Metadata public override Task UpdateActivity(UserActivity? activity) { + localUserPresence = localUserPresence with { Activity = activity }; + if (isWatchingUserPresence.Value) { - userPresences.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); - localUserPresence = localUserPresence with { Activity = activity }; - userPresences[api.LocalUser.Value.Id] = localUserPresence; + if (userPresences.ContainsKey(api.LocalUser.Value.Id)) + userPresences[api.LocalUser.Value.Id] = localUserPresence; } return Task.CompletedTask; @@ -60,11 +61,12 @@ namespace osu.Game.Tests.Visual.Metadata public override Task UpdateStatus(UserStatus? status) { + localUserPresence = localUserPresence with { Status = status }; + if (isWatchingUserPresence.Value) { - userPresences.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); - localUserPresence = localUserPresence with { Status = status }; - userPresences[api.LocalUser.Value.Id] = localUserPresence; + if (userPresences.ContainsKey(api.LocalUser.Value.Id)) + userPresences[api.LocalUser.Value.Id] = localUserPresence; } return Task.CompletedTask; From 41c603b56f0b9d0fce6b2fe03954d88c82644cba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jan 2025 16:41:02 +0900 Subject: [PATCH 11/11] Fix double-retrieval of user presence from dictionary in online display --- .../Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 39023c16f6..bb4c9d96c8 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Dashboard private const float padding = 10; private readonly IBindableList playingUsers = new BindableList(); - private readonly IBindableDictionary onlineUsers = new BindableDictionary(); + private readonly IBindableDictionary onlineUserPresences = new BindableDictionary(); private readonly Dictionary userPanels = new Dictionary(); private SearchContainer userFlow; @@ -106,8 +106,8 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - onlineUsers.BindTo(metadataClient.UserPresences); - onlineUsers.BindCollectionChanged(onUserUpdated, true); + onlineUserPresences.BindTo(metadataClient.UserPresences); + onlineUserPresences.BindCollectionChanged(onUserPresenceUpdated, true); playingUsers.BindTo(spectatorClient.PlayingUsers); playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Dashboard searchTextBox.TakeFocus(); } - private void onUserUpdated(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => + private void onUserPresenceUpdated(object sender, NotifyDictionaryChangedEventArgs e) => Schedule(() => { switch (e.Action) { @@ -142,8 +142,10 @@ namespace osu.Game.Overlays.Dashboard { userFlow.Add(userPanels[userId] = createUserPanel(user).With(p => { - p.Status.Value = onlineUsers.GetValueOrDefault(userId).Status; - p.Activity.Value = onlineUsers.GetValueOrDefault(userId).Activity; + var presence = onlineUserPresences.GetValueOrDefault(userId); + + p.Status.Value = presence.Status; + p.Activity.Value = presence.Activity; })); }); });