Skip to content

Add user card with global/country ranks to login overlay #26340

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

Merged
merged 9 commits into from
Jan 17, 2024
25 changes: 23 additions & 2 deletions osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
Expand All @@ -29,6 +30,9 @@ public partial class TestSceneUserPanel : OsuTestScene
private UserGridPanel boundPanel1;
private TestUserListPanel boundPanel2;

[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);

[Resolved]
private IRulesetStore rulesetStore { get; set; }

Expand Down Expand Up @@ -85,8 +89,25 @@ public void SetUp() => Schedule(() =>
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
IsOnline = false,
LastVisit = DateTimeOffset.Now
})
},
}),
new UserRankPanel(new APIUser
{
Username = @"flyte",
Id = 3103765,
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg",
Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 }
}) { Width = 300 },
new UserRankPanel(new APIUser
{
Username = @"peppy",
Id = 2,
Colour = "99EB47",
CountryCode = CountryCode.AU,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
}) { Width = 300 }
}
};

boundPanel1.Status.BindTo(status);
Expand Down
6 changes: 1 addition & 5 deletions osu.Game/Overlays/Login/LoginPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public partial class LoginPanel : Container
[Resolved]
private OsuColour colours { get; set; } = null!;

private UserGridPanel panel = null!;
private UserDropdown dropdown = null!;

/// <summary>
Expand Down Expand Up @@ -131,7 +130,7 @@ private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule((
Text = LoginPanelStrings.SignedIn,
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
},
panel = new UserGridPanel(api.LocalUser.Value)
new UserRankPanel(api.LocalUser.Value)
{
RelativeSizeAxes = Axes.X,
Action = RequestHide
Expand All @@ -140,9 +139,6 @@ private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule((
},
};

panel.Status.BindTo(api.LocalUser.Value.Status);
panel.Activity.BindTo(api.LocalUser.Value.Activity);

dropdown.Current.BindValueChanged(action =>
{
switch (action.NewValue)
Expand Down
9 changes: 0 additions & 9 deletions osu.Game/Users/ExtendedUserPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#nullable disable

using osuTK;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
Expand Down Expand Up @@ -53,14 +52,6 @@ protected override void LoadComplete()
statusIcon.FinishTransforms();
}

protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false);

protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode)
{
Size = new Vector2(36, 26),
Action = Action,
};

protected Container CreateStatusIcon() => statusIcon = new StatusIcon();

protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren)
Expand Down
41 changes: 27 additions & 14 deletions osu.Game/Users/UserPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Users.Drawables;
using osuTK;

namespace osu.Game.Users
{
Expand Down Expand Up @@ -77,23 +79,18 @@ private void load()
{
Masking = true;

AddRange(new[]
Add(new Box
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider?.Background5 ?? Colours.Gray1
},
Background = new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = User,
},
CreateLayout()
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider?.Background5 ?? Colours.Gray1
});

var background = CreateBackground();
if (background != null)
Add(background);

Add(CreateLayout());

base.Action = ViewProfile = () =>
{
Action?.Invoke();
Expand All @@ -110,6 +107,22 @@ private void load()
Text = User.Username,
};

protected virtual Drawable? CreateBackground() => Background = new UserCoverBackground
Copy link
Member

Choose a reason for hiding this comment

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

This is so weird. So there's one method called CreateX and it's virtual and requires override to do something, then there's three other Create methods which are required to be called by the implementation to actually do something.

At very least this needs xmldoc. Optimally, they shouldn't use the same naming structure.

{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = User
};

protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false);

protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode)
{
Size = new Vector2(36, 26),
Action = Action,
};

public MenuItem[] ContextMenuItems
{
get
Expand Down
212 changes: 212 additions & 0 deletions osu.Game/Users/UserRankPanel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osuTK;

namespace osu.Game.Users
{
/// <summary>
/// User card that shows user's global and country ranks in the bottom.
/// Meant to be used in the toolbar login overlay.
/// </summary>
public partial class UserRankPanel : UserPanel
{
private const int padding = 10;
private const int main_content_height = 80;

public UserRankPanel(APIUser user)
: base(user)
{
AutoSizeAxes = Axes.Y;
CornerRadius = 10;
}

[BackgroundDependencyLoader]
private void load()
{
BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter;
}

protected override Drawable CreateLayout()
{
FillFlowContainer details;

var layout = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Container
{
Name = "Main content",
RelativeSizeAxes = Axes.X,
Height = main_content_height,
CornerRadius = 10,
Masking = true,
Children = new Drawable[]
{
new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = User,
Alpha = 0.3f
},
new GridContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, padding),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.Absolute, padding),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, padding),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
Empty(),
Empty(),
Empty(),
Empty()
Copy link
Member

Choose a reason for hiding this comment

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

what on earth.

Copy link
Member Author

Choose a reason for hiding this comment

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

Padding, similar to how UserGridPanel does it

Copy link
Member

Choose a reason for hiding this comment

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

Please add an inline comment explaining this.

},
new[]
{
Empty(),
CreateAvatar().With(avatar =>
{
avatar.Size = new Vector2(60);
avatar.Masking = true;
avatar.CornerRadius = 6;
}),
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = padding },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension()
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension()
},
Content = new[]
{
new Drawable[]
{
details = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(6),
Children = new Drawable[]
{
CreateFlag(),
}
}
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 hoping this was just bad copy and paste and you haven't used an AI to write this.

Copy link
Member Author

Choose a reason for hiding this comment

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

All of the top layer of the card was pretty much one to one copied from the UserGridPanel. If you're wondering why its a flow - its for the supporter icon which is being added later

Copy link
Member

Choose a reason for hiding this comment

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

Can you add inline comments for places where code doesn't explain itself?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, should I do the same in the UserGridCard as well?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, please do.

},
new Drawable[]
{
CreateUsername().With(username =>
{
username.Anchor = Anchor.CentreLeft;
username.Origin = Anchor.CentreLeft;
})
}
}
}
},
Empty()
}
}
}
}
},
new Container
{
Name = "Bottom content",
Margin = new MarginPadding { Top = main_content_height },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = 80, Vertical = padding },
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension()
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
new Drawable[]
{
new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
Content = User.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"
},
new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
Content = User.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"
}
Copy link
Member

Choose a reason for hiding this comment

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

I dunno. These are never going to update. I'd say they need to update as a minimum requirement to have this added, else it's misleading to the user.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I'll look into it

Copy link
Member

Choose a reason for hiding this comment

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

There probably needs to be a global event which is triggered by the results screen when a change to statistics happens, which can schedule a future update of this card (next time the panel is opened).

Probably out of scope for this PR, but I'd hope to have that as a prereq or follow-up PR so they can be merged together.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could updating APIAccess.LocalUser bindable (and binding the card updates to it) be an option here? To me it feels like updating all of the user info might be better than just statistics

Copy link
Member

Choose a reason for hiding this comment

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

You're probably definitely going to run into the fact that APIUser equality will cause a no-op when you update that bindable.

Maybe separating Statistics into a separate bindable on APIAcess if that is enough to make things work? Then having results screen trigger a apiAccess.RefreshLocalUser() or something, which updates the bindable statistics.

}
}
}
}
}
};

if (User.IsSupporter)
{
details.Add(new SupporterIcon
{
Height = 26,
SupportLevel = User.SupportLevel
});
}

return layout;
}

protected override bool OnHover(HoverEvent e)
{
BorderThickness = 2;
return base.OnHover(e);
}

protected override void OnHoverLost(HoverLostEvent e)
{
BorderThickness = 0;
base.OnHoverLost(e);
}

protected override Drawable? CreateBackground() => null;
}
}