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:
commit
a8456ce9ac
@ -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[]
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>() });
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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).
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user