Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Status and Activity bindables from APIUser #31513

Merged
merged 12 commits into from
Jan 17, 2025
Merged
14 changes: 4 additions & 10 deletions osu.Desktop/DiscordRichPresence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ internal partial class DiscordRichPresence : Component
[Resolved]
private OsuConfigManager config { get; set; } = null!;

private readonly IBindable<UserStatus?> status = new Bindable<UserStatus?>();
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
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
Expand Down Expand Up @@ -108,14 +108,8 @@ protected override void LoadComplete()
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);

user = api.LocalUser.GetBoundCopy();
user.BindValueChanged(u =>
{
status.UnbindBindings();
status.BindTo(u.NewValue.Status);

activity.UnbindBindings();
activity.BindTo(u.NewValue.Activity);
}, true);
status.BindTo(api.Status);
activity.BindTo(api.Activity);

ruleset.BindValueChanged(_ => schedulePresenceUpdate());
status.BindValueChanged(_ => schedulePresenceUpdate());
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void TestLoginSuccess()
AddStep("clear handler", () => dummyAPI.HandleRequest = null);

assertDropdownState(UserAction.Online);
AddStep("change user state", () => dummyAPI.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb);
AddStep("change user state", () => dummyAPI.Status.Value = UserStatus.DoNotDisturb);
assertDropdownState(UserAction.DoNotDisturb);
}

Expand Down
5 changes: 1 addition & 4 deletions osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ private Drawable generateUser(string username, int id, CountryCode countryCode,
CountryCode = countryCode,
CoverUrl = cover,
Colour = color ?? "000000",
Status =
{
Value = UserStatus.Online
},
IsOnline = true
Copy link
Contributor Author

@smoogipoo smoogipoo Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in 20108e3, this is a hack that I expect to go away soon because obviously this has no way to convey user activity & dnd state.

};

return new ClickableAvatar(user, showPanel)
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void SetUp() => Schedule(() =>
Id = 3103765,
CountryCode = CountryCode.JP,
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
Status = { Value = UserStatus.Online }
IsOnline = true
}) { Width = 300 },
boundPanel1 = new UserGridPanel(new APIUser
{
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ protected override void InitialiseDefaults()
SetDefault(OsuSetting.LastProcessedMetadataId, -1);

SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
Copy link
Contributor Author

@smoogipoo smoogipoo Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will throw a non-fatal runtime error if you have the previous null default value in the config:

UserOnlineStatus =

However, the value is set to Online as soon as the user logs in, and never set back to the default value after that.

This is not easy to handle because we don't have a good way to change values from nullable to non-nullable right now (if ever?), so I left it as is and hope this is okay. Adding a migration doesn't help because those are processed after all these defaults are applied.


SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
SetDefault(OsuSetting.EditorTimelineShowBreaks, true);
Expand Down
25 changes: 4 additions & 21 deletions osu.Game/Online/API/APIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public partial class APIAccess : Component, IAPIProvider

public IBindable<APIUser> LocalUser => localUser;
public IBindableList<APIRelation> Friends => friends;
public Bindable<UserStatus> Status { get; } = new Bindable<UserStatus>(UserStatus.Online);
public IBindable<UserActivity> Activity => activity;

public INotificationsClient NotificationsClient { get; }
Expand All @@ -72,9 +73,6 @@ public partial class APIAccess : Component, IAPIProvider

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));

private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
Expand Down Expand Up @@ -110,7 +108,7 @@ public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguratio
authentication.TokenString = config.Get<string>(OsuSetting.Token);
authentication.Token.ValueChanged += onTokenChanged;

config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
config.BindWith(OsuSetting.UserOnlineStatus, Status);

if (HasLogin)
{
Expand All @@ -121,17 +119,6 @@ public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguratio
state.Value = APIState.Connecting;
}

localUser.BindValueChanged(u =>
{
u.OldValue?.Activity.UnbindFrom(activity);
u.NewValue.Activity.BindTo(activity);

u.OldValue?.Status.UnbindFrom(localUserStatus);
u.NewValue.Status.BindTo(localUserStatus);
}, true);

localUserStatus.BindTo(configStatus);

var thread = new Thread(run)
{
Name = "APIAccess",
Expand Down Expand Up @@ -342,10 +329,7 @@ private void attemptConnect()
{
Debug.Assert(ThreadSafety.IsUpdateThread);

me.Status.Value = configStatus.Value ?? UserStatus.Online;

localUser.Value = me;

state.Value = me.SessionVerified ? APIState.Online : APIState.RequiresSecondFactorAuth;
failureCount = 0;
};
Expand Down Expand Up @@ -381,8 +365,7 @@ private void setPlaceholderLocalUser()

localUser.Value = new APIUser
{
Username = ProvidedUsername,
Status = { Value = configStatus.Value ?? UserStatus.Online }
Username = ProvidedUsername
};
}

Expand Down Expand Up @@ -608,7 +591,7 @@ public void Logout()
password = null;
SecondFactorCode = null;
authentication.Clear();
configStatus.Value = UserStatus.Online;
Status.Value = UserStatus.Online;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno about this...

The point of the configStatus / localUserStatus separation was to better control what was getting written to the config. See #26337 and #29186. While this may be functionally equivalent, I find the face-value reading of this of "when the user logs out, their status is set to... online" rather brain-breaky... (Although you could say this was weird to begin with and the value should have been set to null instead here. Guess I didn't think about that in #29186.)

I think I'd prefer the bindable split to still exist, and for the config status to still be nullable. The absolute bare minimum here for me is an explanatory comment though because at face value this just looks completely bugged.

Copy link
Contributor Author

@smoogipoo smoogipoo Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to better control what was getting written to the config

Can you give an example of a scenario where this is important? The way I see it:

  1. We want the status to be read from config when logged in.
  2. We want the status to be written to the config when it changes.

So I'm asking for case (3) where the values should deviate. Is it maybe easier to think of this in the context of the other comment, where I allude to this basically being the config value just exposed in an intuitive place?

As far as I can tell, #29186 is going in the same path as I have here.

Copy link
Collaborator

@bdach bdach Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm asking for case (3) where the values should deviate

Well again I don't think I have any such scenario wherein it functionally matters, but I guess in an idealistic sense, when the user logs out, I'd prefer the "config user status" to be null over Online, because there's no user in sight.

The lack of functional difference is why I'm not gonna push for any material changes in code here, but I believe at least an inline comment is warranted, because it's a line of code that looks very odd without knowing the context of this also backing a config bindable.

Copy link
Contributor Author

@smoogipoo smoogipoo Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the "config user status" to be null over Online

I disagree because the config value is really "what does the user want to appear as" instead of "what is the user's current status". I would maybe even go so far as to say the state shouldn't be reset on logout - that is, if the user "wants to appear as" offline, then when they log in again they should still appear as offline.

Would adjusting the xmldoc to the status to be broadcast for the current user help things?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe even go so far as to say the state shouldn't be reset on logout - that is, if the user "want to appear as" offline, then when they log in again they should still appear as offline

That reset behaviour was intended for cases where multiple users play on a single machine. Whether that is a niche enough use case to not warrant it, I don't know, but it was accepted at review time.

Would adjusting the xmldoc to the status to be broadcast for the current user help things?

Not really, because that's not the part I have issue with - it's the "this gets written to config" part in the logout flow that is obscured / non-obvious with these changes to me specifically. Broadcasting for current user doesn't really have anything to do with config storage in my head.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just had a look at this PR, and I think I prefer the solution offered in 52d1842. If APIAccess can avoid exposing this config setting at all, then that works the best. I don't see an issue with DiscordRichPresence and OnlineMetadataClient reading from the config rather than api.State.

In addition, if Status is still exposed via IAPIProvider, it should be an IBindable. Every write usage should request it from the configuration directly. I've tested and this should work fine.

Copy link
Contributor Author

@smoogipoo smoogipoo Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've re-applied the change with a few other fixes, exposing it as IBindable now.

To be perfectly clear, the only solution I'm fine with is the original presented solution - exposing it as a Bindable directly. I have many reasons for this, which I cannot convey so I'm not going to try anymore.

However, do be aware of one particular footgun. Because there are now two controllers exposing the same thing - one as read-only and one as "write-only", if you ever want to test components that utilise BOTH forms then you will need to both:

  • Create your own OsuConfigManager (or use the testscene's global one - up to you I guess), and set the config value directly, AND...
  • Propagate any values you've set also to DummyAPIAccess.Status (Bindable<>) directly.

I do not have a solution for linking the two - given that DummyAPIAccess is global for the entire test scene, code like:

new DependencyProvidingContainer
{
    CachedDependencies =
    [
        (typeof(OsuConfigManager), new OsuConfigManager(LocalStorage))
    ]
}

... that intends to isolate the config manager per-test, will not work if I simply resolve an OsuConfigManager into DummyAPIAccess like APIAccess does. For now I've exposed it from DummyAPIAccess in some usable way just so that if a case comes up then we immediately have something to work with, even if it is hacky imo. I use this in the subsequent PR here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering: can we just remove Status from IAPIProvider completely?

diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 6c7e7d393f..cedc34dc56 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -54,7 +54,7 @@ internal partial class DiscordRichPresence : Component
         [Resolved]
         private OsuConfigManager config { get; set; } = null!;
 
-        private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
+        private readonly Bindable<UserStatus> status = new Bindable<UserStatus>();
         private readonly IBindable<UserActivity?> activity = new Bindable<UserActivity?>();
         private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
 
@@ -106,9 +106,9 @@ protected override void LoadComplete()
             base.LoadComplete();
 
             config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
+            config.BindWith(OsuSetting.UserOnlineStatus, status);
 
             user = api.LocalUser.GetBoundCopy();
-            status.BindTo(api.Status);
             activity.BindTo(api.Activity);
 
             ruleset.BindValueChanged(_ => schedulePresenceUpdate());
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 3fef2b59cf..b338f4e8cb 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -197,7 +197,6 @@ public void UpdateLocalFriends()
 
         IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
         IBindableList<APIRelation> IAPIProvider.Friends => Friends;
-        IBindable<UserStatus> IAPIProvider.Status => Status;
         IBindable<UserActivity?> IAPIProvider.Activity => Activity;
 
         /// <summary>
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index 9ac7343885..4b87db4ecc 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -24,11 +24,6 @@ public interface IAPIProvider
         /// </summary>
         IBindableList<APIRelation> Friends { get; }
 
-        /// <summary>
-        /// The status for the current user that's broadcast to other players.
-        /// </summary>
-        IBindable<UserStatus> Status { get; }
-
         /// <summary>
         /// The activity for the current user that's broadcast to other players.
         /// </summary>
diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs
index 101307636a..26367c9c5c 100644
--- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs
+++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs
@@ -73,9 +73,9 @@ private void load(IAPIProvider api, OsuConfigManager config)
             }
 
             lastQueueId = config.GetBindable<int>(OsuSetting.LastProcessedMetadataId);
+            userStatus = config.GetBindable<UserStatus>(OsuSetting.UserOnlineStatus);
 
             localUser = api.LocalUser.GetBoundCopy();
-            userStatus = api.Status.GetBoundCopy();
             userActivity = api.Activity.GetBoundCopy()!;
         }
 

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

52d1842 originally did remove the status from IAPIProvider completely, for whatever it's worth.

Copy link
Contributor Author

@smoogipoo smoogipoo Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I've done this now, along with the suggestion from #31524

  • UserStatus moved to OsuConfigManager.
  • UserActivity moved to SessionStatics, as I proposed above.


// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
Schedule(() =>
Expand Down
15 changes: 4 additions & 11 deletions osu.Game/Online/API/DummyAPIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public partial class DummyAPIAccess : Component, IAPIProvider

public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();

public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
public Bindable<UserStatus> Status { get; } = new Bindable<UserStatus>(UserStatus.Online);

public Bindable<UserActivity?> Activity { get; } = new Bindable<UserActivity?>();

public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
Expand Down Expand Up @@ -69,15 +71,6 @@ public partial class DummyAPIAccess : Component, IAPIProvider
/// </summary>
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)
{
request.AttachAPI(this);
Expand Down Expand Up @@ -204,7 +197,7 @@ public void UpdateLocalFriends()

IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
IBindable<UserActivity> IAPIProvider.Activity => Activity;
IBindable<UserActivity?> IAPIProvider.Activity => Activity;

/// <summary>
/// Skip 2FA requirement for next login.
Expand Down
7 changes: 6 additions & 1 deletion osu.Game/Online/API/IAPIProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ public interface IAPIProvider
/// </summary>
IBindableList<APIRelation> Friends { get; }

/// <summary>
/// The current user's status.
/// </summary>
Bindable<UserStatus> Status { get; }

/// <summary>
/// The current user's activity.
/// </summary>
IBindable<UserActivity> Activity { get; }
IBindable<UserActivity?> Activity { get; }

/// <summary>
/// The language supplied by this provider to API requests.
Expand Down
5 changes: 0 additions & 5 deletions osu.Game/Online/API/Requests/Responses/APIUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Extensions;
using osu.Game.Users;

Expand Down Expand Up @@ -56,10 +55,6 @@ public CountryCode CountryCode
set => countryCodeString = value.ToString();
}

public readonly Bindable<UserStatus?> Status = new Bindable<UserStatus?>();

public readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();

[JsonProperty(@"profile_colour")]
public string Colour;

Expand Down
17 changes: 8 additions & 9 deletions osu.Game/Online/Metadata/OnlineMetadataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ public partial class OnlineMetadataClient : MetadataClient
private IHubClientConnector? connector;
private Bindable<int> lastQueueId = null!;
private IBindable<APIUser> localUser = null!;

private IBindable<UserStatus> userStatus = null!;
private IBindable<UserActivity?> userActivity = null!;
private IBindable<UserStatus?>? userStatus;

private HubConnection? connection => connector?.CurrentConnection;

Expand Down Expand Up @@ -75,22 +76,20 @@ private void load(IAPIProvider api, OsuConfigManager config)
lastQueueId = config.GetBindable<int>(OsuSetting.LastProcessedMetadataId);

localUser = api.LocalUser.GetBoundCopy();
userStatus = api.Status.GetBoundCopy();
userActivity = api.Activity.GetBoundCopy()!;
}

protected override void LoadComplete()
{
base.LoadComplete();
localUser.BindValueChanged(_ =>

userStatus.BindValueChanged(status =>
{
if (localUser.Value is not GuestUser)
{
userStatus = localUser.Value.Status.GetBoundCopy();
userStatus.BindValueChanged(status => UpdateStatus(status.NewValue), true);
}
else
userStatus = null;
UpdateStatus(status.NewValue);
}, true);

userActivity.BindValueChanged(activity =>
{
if (localUser.Value is not GuestUser)
Expand All @@ -117,7 +116,7 @@ private void isConnectedChanged(ValueChangedEvent<bool> connected)
if (localUser.Value is not GuestUser)
{
UpdateActivity(userActivity.Value);
UpdateStatus(userStatus?.Value);
UpdateStatus(userStatus.Value);
}

if (lastQueueId.Value >= 0)
Expand Down
23 changes: 11 additions & 12 deletions osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,11 @@ private void onUserUpdated(object sender, NotifyDictionaryChangedEventArgs<int,

Schedule(() =>
{
// explicitly refetch the user's status.
// things may have changed in between the time of scheduling and the time of actual execution.
if (onlineUsers.TryGetValue(userId, out var updatedStatus))
userFlow.Add(userPanels[userId] = createUserPanel(user).With(p =>
{
user.Activity.Value = updatedStatus.Activity;
user.Status.Value = updatedStatus.Status;
}

userFlow.Add(userPanels[userId] = createUserPanel(user));
p.Status.Value = onlineUsers.GetValueOrDefault(userId).Status;
p.Activity.Value = onlineUsers.GetValueOrDefault(userId).Activity;
}));
});
});
}
Expand All @@ -162,8 +158,8 @@ private void onUserUpdated(object sender, NotifyDictionaryChangedEventArgs<int,
{
if (userPanels.TryGetValue(kvp.Key, out var panel))
{
panel.User.Activity.Value = kvp.Value.Activity;
panel.User.Status.Value = kvp.Value.Status;
panel.Activity.Value = kvp.Value.Activity;
panel.Status.Value = kvp.Value.Status;
}
}

Expand Down Expand Up @@ -223,6 +219,9 @@ public partial class OnlineUserPanel : CompositeDrawable, IFilterable
{
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 IEnumerable<LocalisableString> FilterTerms { get; }
Expand Down Expand Up @@ -271,8 +270,8 @@ private void load()
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
// this is SHOCKING
Activity = { BindTarget = User.Activity },
Status = { BindTarget = User.Status },
Activity = { BindTarget = Activity },
Status = { BindTarget = Status },
},
new PurpleRoundedButton
{
Expand Down
19 changes: 5 additions & 14 deletions osu.Game/Overlays/Login/LoginPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Settings;
using osu.Game.Users;
using osuTK;
Expand All @@ -38,9 +37,7 @@ public partial class LoginPanel : Container
/// </summary>
public Action? RequestHide;

private IBindable<APIUser> user = null!;
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();

private readonly Bindable<UserStatus> status = new Bindable<UserStatus>();
private readonly IBindable<APIState> apiState = new Bindable<APIState>();

[Resolved]
Expand Down Expand Up @@ -71,13 +68,7 @@ protected override void LoadComplete()
apiState.BindTo(api.State);
apiState.BindValueChanged(onlineStateChanged, true);

user = api.LocalUser.GetBoundCopy();
user.BindValueChanged(u =>
{
status.UnbindBindings();
status.BindTo(u.NewValue.Status);
}, true);

status.BindTo(api.Status);
status.BindValueChanged(e => updateDropdownCurrent(e.NewValue), true);
}

Expand Down Expand Up @@ -163,17 +154,17 @@ private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule((
switch (action.NewValue)
{
case UserAction.Online:
api.LocalUser.Value.Status.Value = UserStatus.Online;
status.Value = UserStatus.Online;
dropdown.StatusColour = colours.Green;
break;

case UserAction.DoNotDisturb:
api.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb;
status.Value = UserStatus.DoNotDisturb;
dropdown.StatusColour = colours.Red;
break;

case UserAction.AppearOffline:
api.LocalUser.Value.Status.Value = UserStatus.Offline;
status.Value = UserStatus.Offline;
dropdown.StatusColour = colours.Gray7;
break;

Expand Down
Loading