From 5441c02a1a8fa01d1a33fb6b633ac2de5b157740 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 11 Jan 2023 07:11:38 +0300 Subject: [PATCH 1/5] Implement user group badges --- .../Online/TestSceneUserProfileHeader.cs | 1 + .../Online/TestSceneUserProfileOverlay.cs | 5 + .../Online/API/Requests/Responses/APIUser.cs | 3 + .../API/Requests/Responses/APIUserGroup.cs | 37 ++++++ .../Header/Components/GroupInfoContainer.cs | 106 ++++++++++++++++++ .../Profile/Header/TopHeaderContainer.cs | 9 +- 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/Responses/APIUserGroup.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index bfd6372e6f..3c0ea4f590 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -59,6 +59,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 2001, Username = "RankedUser", + Groups = new[] { new APIUserGroup() { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" } }, Statistics = new UserStatistics { IsRanked = true, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 35c7e7bf28..cab883c0da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -27,6 +27,11 @@ namespace osu.Game.Tests.Visual.Online JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, ProfileOrder = new[] { "me" }, + Groups = new[] + { + new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" }, + new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } } + }, Statistics = new UserStatistics { IsRanked = true, diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 9cb0c0704d..0e62b03f1a 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -255,6 +255,9 @@ namespace osu.Game.Online.API.Requests.Responses [CanBeNull] public Dictionary RulesetsStatistics { get; set; } + [JsonProperty("groups")] + public APIUserGroup[] Groups; + public override string ToString() => Username; /// diff --git a/osu.Game/Online/API/Requests/Responses/APIUserGroup.cs b/osu.Game/Online/API/Requests/Responses/APIUserGroup.cs new file mode 100644 index 0000000000..5f943e583e --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserGroup.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserGroup + { + [JsonProperty(@"colour")] + public string Colour { get; set; } = null!; + + [JsonProperty(@"has_listing")] + public bool HasListings { get; set; } + + [JsonProperty(@"has_playmodes")] + public bool HasPlaymodes { get; set; } + + [JsonProperty(@"id")] + public int Id { get; set; } + + [JsonProperty(@"identifier")] + public string Identifier { get; set; } = null!; + + [JsonProperty(@"is_probationary")] + public bool IsProbationary { get; set; } + + [JsonProperty(@"name")] + public string Name { get; set; } = null!; + + [JsonProperty(@"short_name")] + public string ShortName { get; set; } = null!; + + [JsonProperty(@"playmodes")] + public string[]? Playmodes { get; set; } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs new file mode 100644 index 0000000000..363a74b5c1 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class GroupBadge : Container, IHasTooltip + { + public LocalisableString TooltipText { get; } + + public int TextSize { get; set; } = 12; + + private readonly APIUserGroup group; + + public GroupBadge(APIUserGroup group) + { + this.group = group; + + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 8; + + TooltipText = group.Name; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? colourProvider, RulesetStore rulesets) + { + FillFlowContainer innerContainer; + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background6 ?? Colour4.Black + }, + innerContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Padding = new MarginPadding { Vertical = 2, Horizontal = 10 }, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new[] + { + new OsuSpriteText + { + Text = group.ShortName, + Colour = Color4Extensions.FromHex(group.Colour), + Shadow = false, + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true) + } + } + } + }); + + if (group.Playmodes?.Length > 0) + { + innerContainer.AddRange(group.Playmodes.Select(p => + (rulesets.GetRuleset(p)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon => + { + icon.Size = new Vector2(TextSize - 1); + })).ToList() + ); + } + } + } + + public partial class GroupInfoContainer : FillFlowContainer + { + public readonly Bindable User = new Bindable(); + + public GroupInfoContainer() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(2); + + User.BindValueChanged(val => + { + if (val.NewValue?.Groups?.Length > 0) + { + Clear(true); + Children = val.NewValue?.Groups.Select(g => new GroupBadge(g)).ToList(); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index a6501f567f..c26efaf271 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -40,6 +40,7 @@ namespace osu.Game.Overlays.Profile.Header private UpdateableFlag userFlag = null!; private OsuSpriteText userCountryText = null!; private FillFlowContainer userStats = null!; + private GroupInfoContainer groupInfoContainer = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -90,6 +91,7 @@ namespace osu.Game.Overlays.Profile.Header { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), Children = new Drawable[] { usernameText = new OsuSpriteText @@ -98,10 +100,14 @@ namespace osu.Game.Overlays.Profile.Header }, openUserExternally = new ExternalLinkButton { - Margin = new MarginPadding { Left = 5 }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, + groupInfoContainer = new GroupInfoContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } } }, titleText = new OsuSpriteText @@ -184,6 +190,7 @@ namespace osu.Game.Overlays.Profile.Header supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); + groupInfoContainer.User.Value = user; userStats.Clear(); From e0d58d51b61296e3a1b5b0783d8d3bce9838b234 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Jan 2023 16:47:29 +0900 Subject: [PATCH 2/5] Split out classes into own files and rename `GroupInfoContainer` to a flow --- .../{GroupInfoContainer.cs => GroupBadge.cs} | 26 ++------------- .../Header/Components/GroupBadgeFlow.cs | 33 +++++++++++++++++++ .../Profile/Header/TopHeaderContainer.cs | 6 ++-- 3 files changed, 38 insertions(+), 27 deletions(-) rename osu.Game/Overlays/Profile/Header/Components/{GroupInfoContainer.cs => GroupBadge.cs} (74%) create mode 100644 osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs similarity index 74% rename from osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs rename to osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index 363a74b5c1..b469fbbcd1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -1,9 +1,8 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -74,7 +73,7 @@ namespace osu.Game.Overlays.Profile.Header.Components if (group.Playmodes?.Length > 0) { innerContainer.AddRange(group.Playmodes.Select(p => - (rulesets.GetRuleset(p)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon => + (rulesets.GetRuleset((string)p)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon => { icon.Size = new Vector2(TextSize - 1); })).ToList() @@ -82,25 +81,4 @@ namespace osu.Game.Overlays.Profile.Header.Components } } } - - public partial class GroupInfoContainer : FillFlowContainer - { - public readonly Bindable User = new Bindable(); - - public GroupInfoContainer() - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(2); - - User.BindValueChanged(val => - { - if (val.NewValue?.Groups?.Length > 0) - { - Clear(true); - Children = val.NewValue?.Groups.Select(g => new GroupBadge(g)).ToList(); - } - }); - } - } } diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs new file mode 100644 index 0000000000..062cba2581 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class GroupBadgeFlow : FillFlowContainer + { + public readonly Bindable User = new Bindable(); + + public GroupBadgeFlow() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(2); + + User.BindValueChanged(val => + { + if (val.NewValue?.Groups?.Length > 0) + { + Clear(true); + Children = val.NewValue?.Groups.Select(g => new GroupBadge(g)).ToList(); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index c26efaf271..d6f2faf5e5 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Header private UpdateableFlag userFlag = null!; private OsuSpriteText userCountryText = null!; private FillFlowContainer userStats = null!; - private GroupInfoContainer groupInfoContainer = null!; + private GroupBadgeFlow groupBadgeFlow = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - groupInfoContainer = new GroupInfoContainer + groupBadgeFlow = new GroupBadgeFlow { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Profile.Header supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); - groupInfoContainer.User.Value = user; + groupBadgeFlow.User.Value = user; userStats.Clear(); From 318867f486db0895ade53249401de2b697351e08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Jan 2023 16:48:47 +0900 Subject: [PATCH 3/5] Fix previous badges potentially not being cleared if new user has no badges --- .../Profile/Header/Components/GroupBadgeFlow.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs index 062cba2581..8a2c8b4874 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs @@ -20,13 +20,12 @@ namespace osu.Game.Overlays.Profile.Header.Components Direction = FillDirection.Horizontal; Spacing = new Vector2(2); - User.BindValueChanged(val => + User.BindValueChanged(user => { - if (val.NewValue?.Groups?.Length > 0) - { - Clear(true); - Children = val.NewValue?.Groups.Select(g => new GroupBadge(g)).ToList(); - } + Clear(true); + + if (user.NewValue != null) + AddRange(user.NewValue.Groups.Select(g => new GroupBadge(g))); }); } } From b1a13286a34054e7fee38956413b5135a3d73543 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Jan 2023 16:50:37 +0900 Subject: [PATCH 4/5] Remove some redundancies --- osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs | 2 +- osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 3c0ea4f590..15c5afe1db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 2001, Username = "RankedUser", - Groups = new[] { new APIUserGroup() { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" } }, + Groups = new[] { new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" } }, Statistics = new UserStatistics { IsRanked = true, diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index b469fbbcd1..ce56768a72 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Profile.Header.Components if (group.Playmodes?.Length > 0) { innerContainer.AddRange(group.Playmodes.Select(p => - (rulesets.GetRuleset((string)p)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon => + (rulesets.GetRuleset(p)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon => { icon.Size = new Vector2(TextSize - 1); })).ToList() From 2dcc61caf59f9980387aacec456923c2b9d94f72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Jan 2023 17:38:08 +0900 Subject: [PATCH 5/5] Add extra level of nullabiliy checking because NRT is not present --- osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs index 8a2c8b4874..33b3de94db 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadgeFlow.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { Clear(true); - if (user.NewValue != null) + if (user.NewValue?.Groups != null) AddRange(user.NewValue.Groups.Select(g => new GroupBadge(g))); }); }