1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-18 05:42:56 +08:00

Merge pull request #31513 from smoogipoo/remove-status-from-apiuser

Remove `Status` and `Activity` bindables from `APIUser`
This commit is contained in:
Dean Herbert 2025-01-17 17:36:47 +09:00 committed by GitHub
commit a8456ce9ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 186 additions and 183 deletions

View File

@ -51,12 +51,9 @@ namespace osu.Desktop
[Resolved] [Resolved]
private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!; private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!;
[Resolved] private IBindable<DiscordRichPresenceMode> privacyMode = null!;
private OsuConfigManager config { get; set; } = null!; private IBindable<UserStatus> userStatus = null!;
private IBindable<UserActivity?> userActivity = null!;
private readonly IBindable<UserStatus?> status = new Bindable<UserStatus?>();
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
private readonly RichPresence presence = new RichPresence private readonly RichPresence presence = new RichPresence
{ {
@ -71,8 +68,12 @@ namespace osu.Desktop
private IBindable<APIUser>? user; private IBindable<APIUser>? user;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuConfigManager config, SessionStatics session)
{ {
privacyMode = config.GetBindable<DiscordRichPresenceMode>(OsuSetting.DiscordRichPresence);
userStatus = config.GetBindable<UserStatus>(OsuSetting.UserOnlineStatus);
userActivity = session.GetBindable<UserActivity?>(Static.UserOnlineActivity);
client = new DiscordRpcClient(client_id) client = new DiscordRpcClient(client_id)
{ {
// SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation // SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation
@ -105,21 +106,11 @@ namespace osu.Desktop
{ {
base.LoadComplete(); base.LoadComplete();
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
user = api.LocalUser.GetBoundCopy(); user = api.LocalUser.GetBoundCopy();
user.BindValueChanged(u =>
{
status.UnbindBindings();
status.BindTo(u.NewValue.Status);
activity.UnbindBindings();
activity.BindTo(u.NewValue.Activity);
}, true);
ruleset.BindValueChanged(_ => schedulePresenceUpdate()); ruleset.BindValueChanged(_ => schedulePresenceUpdate());
status.BindValueChanged(_ => schedulePresenceUpdate()); userStatus.BindValueChanged(_ => schedulePresenceUpdate());
activity.BindValueChanged(_ => schedulePresenceUpdate()); userActivity.BindValueChanged(_ => schedulePresenceUpdate());
privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
multiplayerClient.RoomUpdated += onRoomUpdated; multiplayerClient.RoomUpdated += onRoomUpdated;
@ -151,13 +142,13 @@ namespace osu.Desktop
if (!client.IsInitialized) if (!client.IsInitialized)
return; return;
if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off) if (!api.IsLoggedIn || userStatus.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off)
{ {
client.ClearPresence(); client.ClearPresence();
return; return;
} }
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb; bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || userStatus.Value == UserStatus.DoNotDisturb;
updatePresence(hideIdentifiableInformation); updatePresence(hideIdentifiableInformation);
client.SetPresence(presence); client.SetPresence(presence);
@ -170,12 +161,12 @@ namespace osu.Desktop
return; return;
// user activity // user activity
if (activity.Value != null) if (userActivity.Value != null)
{ {
presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation)); presence.State = clampLength(userActivity.Value.GetStatus(hideIdentifiableInformation));
presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); 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[] presence.Buttons = new[]
{ {

View File

@ -29,9 +29,7 @@ namespace osu.Game.Tests.Visual.Menus
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private LoginOverlay loginOverlay = null!; private LoginOverlay loginOverlay = null!;
private OsuConfigManager localConfig = null!;
[Resolved]
private OsuConfigManager configManager { get; set; } = null!;
[Cached(typeof(LocalUserStatisticsProvider))] [Cached(typeof(LocalUserStatisticsProvider))]
private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider(); private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider();
@ -39,6 +37,8 @@ namespace osu.Game.Tests.Visual.Menus
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
Child = loginOverlay = new LoginOverlay Child = loginOverlay = new LoginOverlay
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.Menus
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
AddStep("reset online state", () => localConfig.SetValue(OsuSetting.UserOnlineStatus, UserStatus.Online));
AddStep("show login overlay", () => loginOverlay.Show()); AddStep("show login overlay", () => loginOverlay.Show());
} }
@ -89,7 +90,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("clear handler", () => dummyAPI.HandleRequest = null); AddStep("clear handler", () => dummyAPI.HandleRequest = null);
assertDropdownState(UserAction.Online); assertDropdownState(UserAction.Online);
AddStep("change user state", () => dummyAPI.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb); AddStep("change user state", () => localConfig.SetValue(OsuSetting.UserOnlineStatus, UserStatus.DoNotDisturb));
assertDropdownState(UserAction.DoNotDisturb); assertDropdownState(UserAction.DoNotDisturb);
} }
@ -188,31 +189,31 @@ namespace osu.Game.Tests.Visual.Menus
public void TestUncheckingRememberUsernameClearsIt() public void TestUncheckingRememberUsernameClearsIt()
{ {
AddStep("logout", () => API.Logout()); AddStep("logout", () => API.Logout());
AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user")); AddStep("set username", () => localConfig.SetValue(OsuSetting.Username, "test_user"));
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); AddStep("set remember password", () => localConfig.SetValue(OsuSetting.SavePassword, true));
AddStep("uncheck remember username", () => AddStep("uncheck remember username", () =>
{ {
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().First()); InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().First());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("remember username off", () => configManager.Get<bool>(OsuSetting.SaveUsername), () => Is.False); AddAssert("remember username off", () => localConfig.Get<bool>(OsuSetting.SaveUsername), () => Is.False);
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False); AddAssert("remember password off", () => localConfig.Get<bool>(OsuSetting.SavePassword), () => Is.False);
AddAssert("username cleared", () => configManager.Get<string>(OsuSetting.Username), () => Is.Empty); AddAssert("username cleared", () => localConfig.Get<string>(OsuSetting.Username), () => Is.Empty);
} }
[Test] [Test]
public void TestUncheckingRememberPasswordClearsToken() public void TestUncheckingRememberPasswordClearsToken()
{ {
AddStep("logout", () => API.Logout()); AddStep("logout", () => API.Logout());
AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token")); AddStep("set token", () => localConfig.SetValue(OsuSetting.Token, "test_token"));
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true)); AddStep("set remember password", () => localConfig.SetValue(OsuSetting.SavePassword, true));
AddStep("uncheck remember token", () => AddStep("uncheck remember token", () =>
{ {
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().Last()); InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().Last());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False); AddAssert("remember password off", () => localConfig.Get<bool>(OsuSetting.SavePassword), () => Is.False);
AddAssert("token cleared", () => configManager.Get<string>(OsuSetting.Token), () => Is.Empty); AddAssert("token cleared", () => localConfig.Get<string>(OsuSetting.Token), () => Is.Empty);
} }
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Configuration;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -23,17 +23,23 @@ namespace osu.Game.Tests.Visual.Online
[Cached(typeof(IChannelPostTarget))] [Cached(typeof(IChannelPostTarget))]
private PostTarget postTarget { get; set; } private PostTarget postTarget { get; set; }
private DummyAPIAccess api => (DummyAPIAccess)API; private SessionStatics session = null!;
public TestSceneNowPlayingCommand() public TestSceneNowPlayingCommand()
{ {
Add(postTarget = new PostTarget()); Add(postTarget = new PostTarget());
} }
[BackgroundDependencyLoader]
private void load()
{
Dependencies.Cache(session = new SessionStatics());
}
[Test] [Test]
public void TestGenericActivity() public void TestGenericActivity()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(new Room())); AddStep("Set activity", () => session.SetValue<UserActivity>(Static.UserOnlineActivity, new UserActivity.InLobby(new Room())));
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
@ -43,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestEditActivity() public void TestEditActivity()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo())); AddStep("Set activity", () => session.SetValue<UserActivity>(Static.UserOnlineActivity, new UserActivity.EditingBeatmap(new BeatmapInfo())));
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
@ -53,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestPlayActivity() public void TestPlayActivity()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); AddStep("Set activity", () => session.SetValue<UserActivity>(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)));
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
@ -64,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
[TestCase(false)] [TestCase(false)]
public void TestLinkPresence(bool hasOnlineId) public void TestLinkPresence(bool hasOnlineId)
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(new Room())); AddStep("Set activity", () => session.SetValue<UserActivity>(Static.UserOnlineActivity, new UserActivity.InLobby(new Room())));
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
{ {
@ -82,7 +88,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestModPresence() public void TestModPresence()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)); AddStep("Set activity", () => session.SetValue<UserActivity>(Static.UserOnlineActivity, new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo)));
AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModHidden>() }); AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModHidden>() });

View File

@ -62,10 +62,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = countryCode, CountryCode = countryCode,
CoverUrl = cover, CoverUrl = cover,
Colour = color ?? "000000", Colour = color ?? "000000",
Status = IsOnline = true
{
Value = UserStatus.Online
},
}; };
return new ClickableAvatar(user, showPanel) return new ClickableAvatar(user, showPanel)

View File

@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 3103765, Id = 3103765,
CountryCode = CountryCode.JP, CountryCode = CountryCode.JP,
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
Status = { Value = UserStatus.Online } IsOnline = true
}) { Width = 300 }, }) { Width = 300 },
boundPanel1 = new UserGridPanel(new APIUser boundPanel1 = new UserGridPanel(new APIUser
{ {

View File

@ -209,7 +209,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.LastProcessedMetadataId, -1); SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null); SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
SetDefault(OsuSetting.EditorTimelineShowBreaks, true); SetDefault(OsuSetting.EditorTimelineShowBreaks, true);
@ -440,7 +440,12 @@ namespace osu.Game.Configuration
EditorShowSpeedChanges, EditorShowSpeedChanges,
TouchDisableGameplayTaps, TouchDisableGameplayTaps,
ModSelectTextSearchStartsActive, ModSelectTextSearchStartsActive,
/// <summary>
/// The status for the current user to broadcast to other players.
/// </summary>
UserOnlineStatus, UserOnlineStatus,
MultiplayerRoomFilter, MultiplayerRoomFilter,
HideCountryFlags, HideCountryFlags,
EditorTimelineShowTimingChanges, EditorTimelineShowTimingChanges,

View File

@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
@ -30,6 +31,7 @@ namespace osu.Game.Configuration
SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile); SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile);
SetDefault<ScoreInfo>(Static.LastLocalUserScore, null); SetDefault<ScoreInfo>(Static.LastLocalUserScore, null);
SetDefault<ScoreInfo>(Static.LastAppliedOffsetScore, null); SetDefault<ScoreInfo>(Static.LastAppliedOffsetScore, null);
SetDefault<UserActivity>(Static.UserOnlineActivity, null);
} }
/// <summary> /// <summary>
@ -92,5 +94,7 @@ namespace osu.Game.Configuration
/// This is reset when a new challenge is up. /// This is reset when a new challenge is up.
/// </summary> /// </summary>
DailyChallengeIntroPlayed, DailyChallengeIntroPlayed,
UserOnlineActivity,
} }
} }

View File

@ -60,7 +60,6 @@ namespace osu.Game.Online.API
public IBindable<APIUser> LocalUser => localUser; public IBindable<APIUser> LocalUser => localUser;
public IBindableList<APIRelation> Friends => friends; public IBindableList<APIRelation> Friends => friends;
public IBindable<UserActivity> Activity => activity;
public INotificationsClient NotificationsClient { get; } public INotificationsClient NotificationsClient { get; }
@ -70,15 +69,10 @@ namespace osu.Game.Online.API
private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>(); private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>();
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
private readonly Bindable<UserStatus> configStatus = new Bindable<UserStatus>();
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
private readonly Logger log; private readonly Logger log;
public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash) public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
@ -121,17 +115,6 @@ namespace osu.Game.Online.API
state.Value = APIState.Connecting; 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) var thread = new Thread(run)
{ {
Name = "APIAccess", Name = "APIAccess",
@ -342,10 +325,7 @@ namespace osu.Game.Online.API
{ {
Debug.Assert(ThreadSafety.IsUpdateThread); Debug.Assert(ThreadSafety.IsUpdateThread);
me.Status.Value = configStatus.Value ?? UserStatus.Online;
localUser.Value = me; localUser.Value = me;
state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth; state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth;
failureCount = 0; failureCount = 0;
}; };
@ -381,8 +361,7 @@ namespace osu.Game.Online.API
localUser.Value = new APIUser localUser.Value = new APIUser
{ {
Username = ProvidedUsername, Username = ProvidedUsername
Status = { Value = configStatus.Value ?? UserStatus.Online }
}; };
} }
@ -608,6 +587,8 @@ namespace osu.Game.Online.API
password = null; password = null;
SecondFactorCode = null; SecondFactorCode = null;
authentication.Clear(); authentication.Clear();
// Reset the status to be broadcast on the next login, in case multiple players share the same system.
configStatus.Value = UserStatus.Online; 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 // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present

View File

@ -12,7 +12,6 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Notifications.WebSocket; using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Tests; using osu.Game.Tests;
using osu.Game.Users;
namespace osu.Game.Online.API namespace osu.Game.Online.API
{ {
@ -28,8 +27,6 @@ namespace osu.Game.Online.API
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>(); public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient(); public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient; INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
@ -69,15 +66,6 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
public IBindable<APIState> State => state; public IBindable<APIState> State => state;
public DummyAPIAccess()
{
LocalUser.BindValueChanged(u =>
{
u.OldValue?.Activity.UnbindFrom(Activity);
u.NewValue.Activity.BindTo(Activity);
}, true);
}
public virtual void Queue(APIRequest request) public virtual void Queue(APIRequest request)
{ {
request.AttachAPI(this); request.AttachAPI(this);
@ -204,7 +192,6 @@ namespace osu.Game.Online.API
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser; IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
IBindableList<APIRelation> IAPIProvider.Friends => Friends; IBindableList<APIRelation> IAPIProvider.Friends => Friends;
IBindable<UserActivity> IAPIProvider.Activity => Activity;
/// <summary> /// <summary>
/// Skip 2FA requirement for next login. /// Skip 2FA requirement for next login.

View File

@ -8,7 +8,6 @@ using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Notifications.WebSocket; using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Users;
namespace osu.Game.Online.API namespace osu.Game.Online.API
{ {
@ -24,11 +23,6 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
IBindableList<APIRelation> Friends { get; } IBindableList<APIRelation> Friends { get; }
/// <summary>
/// The current user's activity.
/// </summary>
IBindable<UserActivity> Activity { get; }
/// <summary> /// <summary>
/// The language supplied by this provider to API requests. /// The language supplied by this provider to API requests.
/// </summary> /// </summary>

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Users; using osu.Game.Users;
@ -56,10 +55,6 @@ namespace osu.Game.Online.API.Requests.Responses
set => countryCodeString = value.ToString(); set => countryCodeString = value.ToString();
} }
public readonly Bindable<UserStatus?> Status = new Bindable<UserStatus?>();
public readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();
[JsonProperty(@"profile_colour")] [JsonProperty(@"profile_colour")]
public string Colour; public string Colour;

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -33,6 +34,7 @@ namespace osu.Game.Online.Chat
private IBindable<RulesetInfo> currentRuleset { get; set; } = null!; private IBindable<RulesetInfo> currentRuleset { get; set; } = null!;
private readonly Channel? target; private readonly Channel? target;
private IBindable<UserActivity?> userActivity = null!;
/// <summary> /// <summary>
/// Creates a new <see cref="NowPlayingCommand"/> to post the currently-playing beatmap to a parenting <see cref="IChannelPostTarget"/>. /// Creates a new <see cref="NowPlayingCommand"/> to post the currently-playing beatmap to a parenting <see cref="IChannelPostTarget"/>.
@ -43,6 +45,12 @@ namespace osu.Game.Online.Chat
this.target = target; this.target = target;
} }
[BackgroundDependencyLoader]
private void load(SessionStatics session)
{
userActivity = session.GetBindable<UserActivity?>(Static.UserOnlineActivity);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -52,7 +60,7 @@ namespace osu.Game.Online.Chat
int beatmapOnlineID; int beatmapOnlineID;
string beatmapDisplayTitle; string beatmapDisplayTitle;
switch (api.Activity.Value) switch (userActivity.Value)
{ {
case UserActivity.InGame game: case UserActivity.InGame game:
verb = "playing"; verb = "playing";
@ -92,14 +100,14 @@ namespace osu.Game.Online.Chat
string getRulesetPart() 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}>"; return $"<{currentRuleset.Value.Name}>";
} }
string getModPart() 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) if (selectedMods.Value.Count == 0)
{ {

View File

@ -46,7 +46,7 @@ namespace osu.Game.Online
private readonly Bindable<bool> notifyOnFriendPresenceChange = new BindableBool(); private readonly Bindable<bool> notifyOnFriendPresenceChange = new BindableBool();
private readonly IBindableList<APIRelation> friends = new BindableList<APIRelation>(); private readonly IBindableList<APIRelation> friends = new BindableList<APIRelation>();
private readonly IBindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>(); private readonly IBindableDictionary<int, UserPresence> friendPresences = new BindableDictionary<int, UserPresence>();
private readonly HashSet<APIUser> onlineAlertQueue = new HashSet<APIUser>(); private readonly HashSet<APIUser> onlineAlertQueue = new HashSet<APIUser>();
private readonly HashSet<APIUser> offlineAlertQueue = new HashSet<APIUser>(); private readonly HashSet<APIUser> offlineAlertQueue = new HashSet<APIUser>();
@ -63,8 +63,8 @@ namespace osu.Game.Online
friends.BindTo(api.Friends); friends.BindTo(api.Friends);
friends.BindCollectionChanged(onFriendsChanged, true); friends.BindCollectionChanged(onFriendsChanged, true);
friendStates.BindTo(metadataClient.FriendStates); friendPresences.BindTo(metadataClient.FriendPresences);
friendStates.BindCollectionChanged(onFriendStatesChanged, true); friendPresences.BindCollectionChanged(onFriendPresenceChanged, true);
} }
protected override void Update() protected override void Update()
@ -85,7 +85,7 @@ namespace osu.Game.Online
if (friend.TargetUser is not APIUser user) if (friend.TargetUser is not APIUser user)
continue; continue;
if (friendStates.TryGetValue(friend.TargetID, out _)) if (friendPresences.TryGetValue(friend.TargetID, out _))
markUserOnline(user); markUserOnline(user);
} }
@ -105,7 +105,7 @@ namespace osu.Game.Online
} }
} }
private void onFriendStatesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e) private void onFriendPresenceChanged(object? sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e)
{ {
switch (e.Action) switch (e.Action)
{ {

View File

@ -37,15 +37,20 @@ namespace osu.Game.Online.Metadata
/// </summary> /// </summary>
public abstract IBindable<bool> IsWatchingUserPresence { get; } public abstract IBindable<bool> IsWatchingUserPresence { get; }
/// <summary>
/// The <see cref="UserPresence"/> information about the current user.
/// </summary>
public abstract UserPresence LocalUserPresence { get; }
/// <summary> /// <summary>
/// Dictionary keyed by user ID containing all of the <see cref="UserPresence"/> information about currently online users received from the server. /// Dictionary keyed by user ID containing all of the <see cref="UserPresence"/> information about currently online users received from the server.
/// </summary> /// </summary>
public abstract IBindableDictionary<int, UserPresence> UserStates { get; } public abstract IBindableDictionary<int, UserPresence> UserPresences { get; }
/// <summary> /// <summary>
/// Dictionary keyed by user ID containing all of the <see cref="UserPresence"/> information about currently online friends received from the server. /// Dictionary keyed by user ID containing all of the <see cref="UserPresence"/> information about currently online friends received from the server.
/// </summary> /// </summary>
public abstract IBindableDictionary<int, UserPresence> FriendStates { get; } public abstract IBindableDictionary<int, UserPresence> FriendPresences { get; }
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task UpdateActivity(UserActivity? activity); public abstract Task UpdateActivity(UserActivity? activity);

View File

@ -23,22 +23,28 @@ namespace osu.Game.Online.Metadata
public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence; public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence;
private readonly BindableBool isWatchingUserPresence = new BindableBool(); private readonly BindableBool isWatchingUserPresence = new BindableBool();
public override IBindableDictionary<int, UserPresence> UserStates => userStates; public override UserPresence LocalUserPresence => localUserPresence;
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>(); private UserPresence localUserPresence;
public override IBindableDictionary<int, UserPresence> FriendStates => friendStates; public override IBindableDictionary<int, UserPresence> UserPresences => userPresences;
private readonly BindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>(); private readonly BindableDictionary<int, UserPresence> userPresences = new BindableDictionary<int, UserPresence>();
public override IBindableDictionary<int, UserPresence> FriendPresences => friendPresences;
private readonly BindableDictionary<int, UserPresence> friendPresences = new BindableDictionary<int, UserPresence>();
public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo; public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>(); private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
private readonly string endpoint; private readonly string endpoint;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private IHubClientConnector? connector; private IHubClientConnector? connector;
private Bindable<int> lastQueueId = null!; private Bindable<int> lastQueueId = null!;
private IBindable<APIUser> localUser = null!; private IBindable<APIUser> localUser = null!;
private IBindable<UserStatus> userStatus = null!;
private IBindable<UserActivity?> userActivity = null!; private IBindable<UserActivity?> userActivity = null!;
private IBindable<UserStatus?>? userStatus;
private HubConnection? connection => connector?.CurrentConnection; private HubConnection? connection => connector?.CurrentConnection;
@ -48,7 +54,7 @@ namespace osu.Game.Online.Metadata
} }
[BackgroundDependencyLoader] [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. // 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. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code.
@ -72,25 +78,22 @@ namespace osu.Game.Online.Metadata
IsConnected.BindValueChanged(isConnectedChanged, true); IsConnected.BindValueChanged(isConnectedChanged, true);
} }
lastQueueId = config.GetBindable<int>(OsuSetting.LastProcessedMetadataId);
localUser = api.LocalUser.GetBoundCopy(); localUser = api.LocalUser.GetBoundCopy();
userActivity = api.Activity.GetBoundCopy()!; lastQueueId = config.GetBindable<int>(OsuSetting.LastProcessedMetadataId);
userStatus = config.GetBindable<UserStatus>(OsuSetting.UserOnlineStatus);
userActivity = session.GetBindable<UserActivity?>(Static.UserOnlineActivity);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
localUser.BindValueChanged(_ =>
userStatus.BindValueChanged(status =>
{ {
if (localUser.Value is not GuestUser) if (localUser.Value is not GuestUser)
{ UpdateStatus(status.NewValue);
userStatus = localUser.Value.Status.GetBoundCopy();
userStatus.BindValueChanged(status => UpdateStatus(status.NewValue), true);
}
else
userStatus = null;
}, true); }, true);
userActivity.BindValueChanged(activity => userActivity.BindValueChanged(activity =>
{ {
if (localUser.Value is not GuestUser) if (localUser.Value is not GuestUser)
@ -107,9 +110,10 @@ namespace osu.Game.Online.Metadata
Schedule(() => Schedule(() =>
{ {
isWatchingUserPresence.Value = false; isWatchingUserPresence.Value = false;
userStates.Clear(); userPresences.Clear();
friendStates.Clear(); friendPresences.Clear();
dailyChallengeInfo.Value = null; dailyChallengeInfo.Value = null;
localUserPresence = default;
}); });
return; return;
} }
@ -117,7 +121,7 @@ namespace osu.Game.Online.Metadata
if (localUser.Value is not GuestUser) if (localUser.Value is not GuestUser)
{ {
UpdateActivity(userActivity.Value); UpdateActivity(userActivity.Value);
UpdateStatus(userStatus?.Value); UpdateStatus(userStatus.Value);
} }
if (lastQueueId.Value >= 0) if (lastQueueId.Value >= 0)
@ -202,9 +206,19 @@ namespace osu.Game.Online.Metadata
Schedule(() => Schedule(() =>
{ {
if (presence?.Status != null) if (presence?.Status != null)
userStates[userId] = presence.Value; {
if (userId == api.LocalUser.Value.OnlineID)
localUserPresence = presence.Value;
else
userPresences[userId] = presence.Value;
}
else else
userStates.Remove(userId); {
if (userId == api.LocalUser.Value.OnlineID)
localUserPresence = default;
else
userPresences.Remove(userId);
}
}); });
return Task.CompletedTask; return Task.CompletedTask;
@ -215,9 +229,9 @@ namespace osu.Game.Online.Metadata
Schedule(() => Schedule(() =>
{ {
if (presence?.Status != null) if (presence?.Status != null)
friendStates[userId] = presence.Value; friendPresences[userId] = presence.Value;
else else
friendStates.Remove(userId); friendPresences.Remove(userId);
}); });
return Task.CompletedTask; return Task.CompletedTask;
@ -242,7 +256,7 @@ namespace osu.Game.Online.Metadata
throw new OperationCanceledException(); throw new OperationCanceledException();
// must be scheduled before any remote calls to avoid mis-ordering. // must be scheduled before any remote calls to avoid mis-ordering.
Schedule(() => userStates.Clear()); Schedule(() => userPresences.Clear());
Debug.Assert(connection != null); Debug.Assert(connection != null);
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false);
Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network);

View File

@ -211,6 +211,8 @@ namespace osu.Game
private Bindable<float> uiScale; private Bindable<float> uiScale;
private Bindable<UserActivity> configUserActivity;
private Bindable<string> configSkin; private Bindable<string> configSkin;
private readonly string[] args; private readonly string[] args;
@ -391,6 +393,8 @@ namespace osu.Game
Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName; Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName;
configUserActivity = SessionStatics.GetBindable<UserActivity>(Static.UserOnlineActivity);
configSkin = LocalConfig.GetBindable<string>(OsuSetting.Skin); configSkin = LocalConfig.GetBindable<string>(OsuSetting.Skin);
// Transfer skin from config to realm instance once on startup. // Transfer skin from config to realm instance once on startup.
@ -1588,14 +1592,14 @@ namespace osu.Game
{ {
backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility);
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
API.Activity.UnbindFrom(currentOsuScreen.Activity); configUserActivity.UnbindFrom(currentOsuScreen.Activity);
} }
if (newScreen is IOsuScreen newOsuScreen) if (newScreen is IOsuScreen newOsuScreen)
{ {
backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility);
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
API.Activity.BindTo(newOsuScreen.Activity); configUserActivity.BindTo(newOsuScreen.Activity);
GlobalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = newOsuScreen.HideMenuCursorOnNonMouseInput; GlobalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = newOsuScreen.HideMenuCursorOnNonMouseInput;

View File

@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Dashboard
private const float padding = 10; private const float padding = 10;
private readonly IBindableList<int> playingUsers = new BindableList<int>(); private readonly IBindableList<int> playingUsers = new BindableList<int>();
private readonly IBindableDictionary<int, UserPresence> onlineUsers = new BindableDictionary<int, UserPresence>(); private readonly IBindableDictionary<int, UserPresence> onlineUserPresences = new BindableDictionary<int, UserPresence>();
private readonly Dictionary<int, OnlineUserPanel> userPanels = new Dictionary<int, OnlineUserPanel>(); private readonly Dictionary<int, OnlineUserPanel> userPanels = new Dictionary<int, OnlineUserPanel>();
private SearchContainer<OnlineUserPanel> userFlow; private SearchContainer<OnlineUserPanel> userFlow;
@ -106,8 +106,8 @@ namespace osu.Game.Overlays.Dashboard
{ {
base.LoadComplete(); base.LoadComplete();
onlineUsers.BindTo(metadataClient.UserStates); onlineUserPresences.BindTo(metadataClient.UserPresences);
onlineUsers.BindCollectionChanged(onUserUpdated, true); onlineUserPresences.BindCollectionChanged(onUserPresenceUpdated, true);
playingUsers.BindTo(spectatorClient.PlayingUsers); playingUsers.BindTo(spectatorClient.PlayingUsers);
playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); playingUsers.BindCollectionChanged(onPlayingUsersChanged, true);
@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Dashboard
searchTextBox.TakeFocus(); searchTextBox.TakeFocus();
} }
private void onUserUpdated(object sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e) => Schedule(() => private void onUserPresenceUpdated(object sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e) => Schedule(() =>
{ {
switch (e.Action) switch (e.Action)
{ {
@ -140,15 +140,13 @@ namespace osu.Game.Overlays.Dashboard
Schedule(() => Schedule(() =>
{ {
// explicitly refetch the user's status. userFlow.Add(userPanels[userId] = createUserPanel(user).With(p =>
// things may have changed in between the time of scheduling and the time of actual execution.
if (onlineUsers.TryGetValue(userId, out var updatedStatus))
{ {
user.Activity.Value = updatedStatus.Activity; var presence = onlineUserPresences.GetValueOrDefault(userId);
user.Status.Value = updatedStatus.Status;
}
userFlow.Add(userPanels[userId] = createUserPanel(user)); p.Status.Value = presence.Status;
p.Activity.Value = presence.Activity;
}));
}); });
}); });
} }
@ -162,8 +160,8 @@ namespace osu.Game.Overlays.Dashboard
{ {
if (userPanels.TryGetValue(kvp.Key, out var panel)) if (userPanels.TryGetValue(kvp.Key, out var panel))
{ {
panel.User.Activity.Value = kvp.Value.Activity; panel.Activity.Value = kvp.Value.Activity;
panel.User.Status.Value = kvp.Value.Status; panel.Status.Value = kvp.Value.Status;
} }
} }
@ -223,6 +221,9 @@ namespace osu.Game.Overlays.Dashboard
{ {
public readonly APIUser User; public readonly APIUser User;
public readonly Bindable<UserStatus?> Status = new Bindable<UserStatus?>();
public readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();
public BindableBool CanSpectate { get; } = new BindableBool(); public BindableBool CanSpectate { get; } = new BindableBool();
public IEnumerable<LocalisableString> FilterTerms { get; } public IEnumerable<LocalisableString> FilterTerms { get; }
@ -271,8 +272,8 @@ namespace osu.Game.Overlays.Dashboard
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
// this is SHOCKING // this is SHOCKING
Activity = { BindTarget = User.Activity }, Activity = { BindTarget = Activity },
Status = { BindTarget = User.Status }, Status = { BindTarget = Status },
}, },
new PurpleRoundedButton new PurpleRoundedButton
{ {

View File

@ -9,13 +9,13 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -38,14 +38,15 @@ namespace osu.Game.Overlays.Login
/// </summary> /// </summary>
public Action? RequestHide; public Action? RequestHide;
private IBindable<APIUser> user = null!;
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
private readonly IBindable<APIState> apiState = new Bindable<APIState>(); private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private readonly Bindable<UserStatus> configUserStatus = new Bindable<UserStatus>();
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private IAPIProvider api { get; set; } = null!;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
public bool Bounding public bool Bounding
@ -68,17 +69,11 @@ namespace osu.Game.Overlays.Login
{ {
base.LoadComplete(); base.LoadComplete();
config.BindWith(OsuSetting.UserOnlineStatus, configUserStatus);
configUserStatus.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true);
apiState.BindTo(api.State); apiState.BindTo(api.State);
apiState.BindValueChanged(onlineStateChanged, true); apiState.BindValueChanged(onlineStateChanged, true);
user = api.LocalUser.GetBoundCopy();
user.BindValueChanged(u =>
{
status.UnbindBindings();
status.BindTo(u.NewValue.Status);
}, true);
status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true);
} }
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() => private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
@ -157,23 +152,23 @@ namespace osu.Game.Overlays.Login
}, },
}; };
updateDropdownCurrent(status.Value); updateDropdownCurrent(configUserStatus.Value);
dropdown.Current.BindValueChanged(action => dropdown.Current.BindValueChanged(action =>
{ {
switch (action.NewValue) switch (action.NewValue)
{ {
case UserAction.Online: case UserAction.Online:
api.LocalUser.Value.Status.Value = UserStatus.Online; configUserStatus.Value = UserStatus.Online;
dropdown.StatusColour = colours.Green; dropdown.StatusColour = colours.Green;
break; break;
case UserAction.DoNotDisturb: case UserAction.DoNotDisturb:
api.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb; configUserStatus.Value = UserStatus.DoNotDisturb;
dropdown.StatusColour = colours.Red; dropdown.StatusColour = colours.Red;
break; break;
case UserAction.AppearOffline: case UserAction.AppearOffline:
api.LocalUser.Value.Status.Value = UserStatus.Offline; configUserStatus.Value = UserStatus.Offline;
dropdown.StatusColour = colours.Gray7; dropdown.StatusColour = colours.Gray7;
break; break;

View File

@ -74,7 +74,7 @@ namespace osu.Game.Screens
/// <summary> /// <summary>
/// The current <see cref="UserActivity"/> for this screen. /// The current <see cref="UserActivity"/> for this screen.
/// </summary> /// </summary>
IBindable<UserActivity> Activity { get; } Bindable<UserActivity> Activity { get; }
/// <summary> /// <summary>
/// The amount of parallax to be applied while this screen is displayed. /// The amount of parallax to be applied while this screen is displayed.

View File

@ -81,7 +81,7 @@ namespace osu.Game.Screens
/// </summary> /// </summary>
protected readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>(); protected readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();
IBindable<UserActivity> IOsuScreen.Activity => Activity; Bindable<UserActivity> IOsuScreen.Activity => Activity;
/// <summary> /// <summary>
/// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children). /// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children).

View File

@ -19,11 +19,14 @@ namespace osu.Game.Tests.Visual.Metadata
public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence; public override IBindable<bool> IsWatchingUserPresence => isWatchingUserPresence;
private readonly BindableBool isWatchingUserPresence = new BindableBool(); private readonly BindableBool isWatchingUserPresence = new BindableBool();
public override IBindableDictionary<int, UserPresence> UserStates => userStates; public override UserPresence LocalUserPresence => localUserPresence;
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>(); private UserPresence localUserPresence;
public override IBindableDictionary<int, UserPresence> FriendStates => friendStates; public override IBindableDictionary<int, UserPresence> UserPresences => userPresences;
private readonly BindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>(); private readonly BindableDictionary<int, UserPresence> userPresences = new BindableDictionary<int, UserPresence>();
public override IBindableDictionary<int, UserPresence> FriendPresences => friendPresences;
private readonly BindableDictionary<int, UserPresence> friendPresences = new BindableDictionary<int, UserPresence>();
public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo; public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>(); private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
@ -45,11 +48,12 @@ namespace osu.Game.Tests.Visual.Metadata
public override Task UpdateActivity(UserActivity? activity) public override Task UpdateActivity(UserActivity? activity)
{ {
localUserPresence = localUserPresence with { Activity = activity };
if (isWatchingUserPresence.Value) if (isWatchingUserPresence.Value)
{ {
userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); if (userPresences.ContainsKey(api.LocalUser.Value.Id))
localUserPresence = localUserPresence with { Activity = activity }; userPresences[api.LocalUser.Value.Id] = localUserPresence;
userStates[api.LocalUser.Value.Id] = localUserPresence;
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -57,11 +61,12 @@ namespace osu.Game.Tests.Visual.Metadata
public override Task UpdateStatus(UserStatus? status) public override Task UpdateStatus(UserStatus? status)
{ {
localUserPresence = localUserPresence with { Status = status };
if (isWatchingUserPresence.Value) if (isWatchingUserPresence.Value)
{ {
userStates.TryGetValue(api.LocalUser.Value.Id, out var localUserPresence); if (userPresences.ContainsKey(api.LocalUser.Value.Id))
localUserPresence = localUserPresence with { Status = status }; userPresences[api.LocalUser.Value.Id] = localUserPresence;
userStates[api.LocalUser.Value.Id] = localUserPresence;
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -71,10 +76,20 @@ namespace osu.Game.Tests.Visual.Metadata
{ {
if (isWatchingUserPresence.Value) if (isWatchingUserPresence.Value)
{ {
if (presence.HasValue) if (presence?.Status != null)
userStates[userId] = presence.Value; {
if (userId == api.LocalUser.Value.OnlineID)
localUserPresence = presence.Value;
else
userPresences[userId] = presence.Value;
}
else else
userStates.Remove(userId); {
if (userId == api.LocalUser.Value.OnlineID)
localUserPresence = default;
else
userPresences.Remove(userId);
}
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -83,9 +98,9 @@ namespace osu.Game.Tests.Visual.Metadata
public override Task FriendPresenceUpdated(int userId, UserPresence? presence) public override Task FriendPresenceUpdated(int userId, UserPresence? presence)
{ {
if (presence.HasValue) if (presence.HasValue)
friendStates[userId] = presence.Value; friendPresences[userId] = presence.Value;
else else
friendStates.Remove(userId); friendPresences.Remove(userId);
return Task.CompletedTask; return Task.CompletedTask;
} }