Merge pull request #22006 from bdach/user-profile/ruleset-switching

Add ruleset switching to user profile overlay
This commit is contained in:
Dean Herbert 2023-01-12 13:54:13 +09:00 committed by GitHub
commit 7cf58190a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 141 additions and 57 deletions

View File

@ -11,6 +11,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.Online
{
@ -38,8 +39,8 @@ namespace osu.Game.Tests.Visual.Online
Child = section = new HistoricalSection(),
});
AddStep("Show peppy", () => section.User.Value = new UserProfileData(new APIUser { Id = 2 }));
AddStep("Show WubWoofWolf", () => section.User.Value = new UserProfileData(new APIUser { Id = 39828 }));
AddStep("Show peppy", () => section.User.Value = new UserProfileData(new APIUser { Id = 2 }, new OsuRuleset().RulesetInfo));
AddStep("Show WubWoofWolf", () => section.User.Value = new UserProfileData(new APIUser { Id = 39828 }, new OsuRuleset().RulesetInfo));
}
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Testing;
using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.Online
{
@ -44,49 +45,49 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestNullValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_null_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_null_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestEmptyValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_empty_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_empty_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestOneValue()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_one_value));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_one_value, new OsuRuleset().RulesetInfo));
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestTwoValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_two_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_two_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestConstantValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_constant_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_constant_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestConstantZeroValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_zero_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_zero_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestFilledValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_filled_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_filled_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is visible", () => section.Alpha == 1);
AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlayCounts.Length == getChartValuesLength());
}
@ -94,7 +95,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestMissingValues()
{
AddStep("Load user", () => user.Value = new UserProfileData(user_with_missing_values));
AddStep("Load user", () => user.Value = new UserProfileData(user_with_missing_values, new OsuRuleset().RulesetInfo));
AddAssert("Section is visible", () => section.Alpha == 1);
AddAssert("Array length is 7", () => getChartValuesLength() == 7);
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile;
namespace osu.Game.Tests.Visual.Online
{
@ -21,25 +22,24 @@ namespace osu.Game.Tests.Visual.Online
public TestSceneProfileRulesetSelector()
{
ProfileRulesetSelector selector;
var user = new Bindable<APIUser?>();
var user = new Bindable<UserProfileData?>();
Child = selector = new ProfileRulesetSelector
Child = new ProfileRulesetSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = { BindTarget = user }
};
AddStep("User on osu ruleset", () => user.Value = new UserProfileData(new APIUser { Id = 0, PlayMode = "osu" }, new OsuRuleset().RulesetInfo));
AddStep("User on taiko ruleset", () => user.Value = new UserProfileData(new APIUser { Id = 1, PlayMode = "osu" }, new TaikoRuleset().RulesetInfo));
AddStep("User on catch ruleset", () => user.Value = new UserProfileData(new APIUser { Id = 2, PlayMode = "osu" }, new CatchRuleset().RulesetInfo));
AddStep("User on mania ruleset", () => user.Value = new UserProfileData(new APIUser { Id = 3, PlayMode = "osu" }, new ManiaRuleset().RulesetInfo));
AddStep("set osu! as default", () => selector.SetDefaultRuleset(new OsuRuleset().RulesetInfo));
AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo));
AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo));
AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo));
AddStep("User with osu as default", () => user.Value = new UserProfileData(new APIUser { Id = 0, PlayMode = "osu" }, new OsuRuleset().RulesetInfo));
AddStep("User with taiko as default", () => user.Value = new UserProfileData(new APIUser { Id = 1, PlayMode = "taiko" }, new OsuRuleset().RulesetInfo));
AddStep("User with catch as default", () => user.Value = new UserProfileData(new APIUser { Id = 2, PlayMode = "fruits" }, new OsuRuleset().RulesetInfo));
AddStep("User with mania as default", () => user.Value = new UserProfileData(new APIUser { Id = 3, PlayMode = "mania" }, new OsuRuleset().RulesetInfo));
AddStep("User with osu as default", () => user.Value = new APIUser { Id = 0, PlayMode = "osu" });
AddStep("User with taiko as default", () => user.Value = new APIUser { Id = 1, PlayMode = "taiko" });
AddStep("User with catch as default", () => user.Value = new APIUser { Id = 2, PlayMode = "fruits" });
AddStep("User with mania as default", () => user.Value = new APIUser { Id = 3, PlayMode = "mania" });
AddStep("null user", () => user.Value = null);
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Rulesets.Osu;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
@ -29,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestBasic()
{
AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER));
AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo));
}
[Test]
@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "IAmOnline",
LastVisit = DateTimeOffset.Now,
IsOnline = true,
}));
}, new OsuRuleset().RulesetInfo));
AddStep("Show offline user", () => header.User.Value = new UserProfileData(new APIUser
{
@ -49,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "IAmOffline",
LastVisit = DateTimeOffset.Now.AddDays(-10),
IsOnline = false,
}));
}, new OsuRuleset().RulesetInfo));
}
[Test]
@ -71,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
},
}
}));
}, new OsuRuleset().RulesetInfo));
AddStep("Show unranked user", () => header.User.Value = new UserProfileData(new APIUser
{
@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.Online
Data = Enumerable.Range(2345, 85).ToArray()
},
}
}));
}, new OsuRuleset().RulesetInfo));
}
}
}

View File

@ -51,6 +51,31 @@ namespace osu.Game.Tests.Visual.Online
});
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
AddToggleStep("toggle visibility", visible => profile.State.Value = visible ? Visibility.Visible : Visibility.Hidden);
AddStep("log out", () => dummyAPI.Logout());
AddStep("log back in", () => dummyAPI.Login("username", "password"));
}
[Test]
public void TestLoading()
{
GetUserRequest pendingRequest = null!;
AddStep("set up request handling", () =>
{
dummyAPI.HandleRequest = req =>
{
if (req is GetUserRequest getUserRequest)
{
pendingRequest = getUserRequest;
return true;
}
return false;
};
});
AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 }));
AddWaitStep("wait some", 3);
AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER));
}
public static readonly APIUser TEST_USER = new APIUser
@ -103,6 +128,7 @@ namespace osu.Game.Tests.Visual.Online
Title = "osu!volunteer",
Colour = "ff0000",
Achievements = Array.Empty<APIUserAchievement>(),
PlayMode = "osu"
};
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.Online
{
@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online
}
});
AddStep("Show cookiezi", () => ranks.User.Value = new UserProfileData(new APIUser { Id = 124493 }));
AddStep("Show cookiezi", () => ranks.User.Value = new UserProfileData(new APIUser { Id = 124493 }, new OsuRuleset().RulesetInfo));
}
}
}

View File

@ -1,22 +1,39 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Extensions;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class ProfileRulesetSelector : OverlayRulesetSelector
{
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
[Resolved]
private UserProfileOverlay? profileOverlay { get; set; }
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu").AsNonNull()), true);
User.BindValueChanged(user => updateState(user.NewValue), true);
Current.BindValueChanged(ruleset =>
{
if (User.Value != null && !ruleset.NewValue.Equals(User.Value.Ruleset))
profileOverlay?.ShowUser(User.Value.User, ruleset.NewValue);
});
}
private void updateState(UserProfileData? user)
{
Current.Value = Items.SingleOrDefault(ruleset => user?.Ruleset.MatchesOnlineID(ruleset) == true);
SetDefaultRuleset(Rulesets.GetRuleset(user?.User.PlayMode ?? @"osu").AsNonNull());
}
public void SetDefaultRuleset(RulesetInfo ruleset)

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Overlays.Profile.Header;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
@ -35,6 +36,13 @@ namespace osu.Game.Overlays.Profile
// todo: pending implementation.
// TabControl.AddItem(LayoutStrings.HeaderUsersModding);
TabControlContainer.Add(new ProfileRulesetSelector
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
User = { BindTarget = User }
});
// Haphazardly guaranteed by OverlayHeader constructor (see CreateBackground / CreateContent).
Debug.Assert(centreHeaderContainer != null);
Debug.Assert(detailHeaderContainer != null);

View File

@ -64,8 +64,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
}
}
protected override APIRequest<List<APIBeatmapSet>> CreateRequest(APIUser user, PaginationParameters pagination) =>
new GetUserBeatmapsRequest(user.Id, type, pagination);
protected override APIRequest<List<APIBeatmapSet>> CreateRequest(UserProfileData user, PaginationParameters pagination) =>
new GetUserBeatmapsRequest(user.User.Id, type, pagination);
protected override Drawable? CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0
? new BeatmapCardNormal(model)

View File

@ -29,8 +29,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
protected override int GetCount(APIUser user) => user.BeatmapPlayCountsCount;
protected override APIRequest<List<APIUserMostPlayedBeatmap>> CreateRequest(APIUser user, PaginationParameters pagination) =>
new GetUserMostPlayedBeatmapsRequest(user.Id, pagination);
protected override APIRequest<List<APIUserMostPlayedBeatmap>> CreateRequest(UserProfileData user, PaginationParameters pagination) =>
new GetUserMostPlayedBeatmapsRequest(user.User.Id, pagination);
protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap mostPlayed) =>
new DrawableMostPlayedBeatmap(mostPlayed);

View File

@ -8,7 +8,6 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.API;
using System.Collections.Generic;
using osu.Game.Resources.Localisation.Web;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Overlays.Profile.Sections.Kudosu
{
@ -19,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
{
}
protected override APIRequest<List<APIKudosuHistory>> CreateRequest(APIUser user, PaginationParameters pagination)
=> new GetUserKudosuHistoryRequest(user.Id, pagination);
protected override APIRequest<List<APIKudosuHistory>> CreateRequest(UserProfileData user, PaginationParameters pagination)
=> new GetUserKudosuHistoryRequest(user.User.Id, pagination);
protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item);
}

View File

@ -109,14 +109,14 @@ namespace osu.Game.Overlays.Profile.Sections
private void showMore()
{
if (User.Value?.User == null)
if (User.Value == null)
return;
loadCancellation = new CancellationTokenSource();
CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount);
retrievalRequest = CreateRequest(User.Value.User, CurrentPage.Value);
retrievalRequest = CreateRequest(User.Value, CurrentPage.Value);
retrievalRequest.Success += items => UpdateItems(items, loadCancellation);
api.Queue(retrievalRequest);
@ -154,7 +154,7 @@ namespace osu.Game.Overlays.Profile.Sections
{
}
protected abstract APIRequest<List<TModel>> CreateRequest(APIUser user, PaginationParameters pagination);
protected abstract APIRequest<List<TModel>> CreateRequest(UserProfileData user, PaginationParameters pagination);
protected abstract Drawable? CreateDrawableItem(TModel model);

View File

@ -60,8 +60,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
base.OnItemsReceived(items);
}
protected override APIRequest<List<SoloScoreInfo>> CreateRequest(APIUser user, PaginationParameters pagination) =>
new GetUserScoresRequest(user.Id, type, pagination);
protected override APIRequest<List<SoloScoreInfo>> CreateRequest(UserProfileData user, PaginationParameters pagination) =>
new GetUserScoresRequest(user.User.Id, type, pagination, user.Ruleset);
private int drawableItemIndex;

View File

@ -10,7 +10,6 @@ using System.Collections.Generic;
using osuTK;
using osu.Framework.Allocation;
using osu.Game.Resources.Localisation.Web;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Overlays.Profile.Sections.Recent
{
@ -27,8 +26,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
ItemsContainer.Spacing = new Vector2(0, 8);
}
protected override APIRequest<List<APIRecentActivity>> CreateRequest(APIUser user, PaginationParameters pagination) =>
new GetUserRecentActivitiesRequest(user.Id, pagination);
protected override APIRequest<List<APIRecentActivity>> CreateRequest(UserProfileData user, PaginationParameters pagination) =>
new GetUserRecentActivitiesRequest(user.User.Id, pagination);
protected override Drawable CreateDrawableItem(APIRecentActivity model) => new DrawableRecentActivity(model);
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Profile
{
@ -15,11 +16,15 @@ namespace osu.Game.Overlays.Profile
/// </summary>
public APIUser User { get; }
// TODO: add ruleset
/// <summary>
/// The ruleset that the user profile is being shown with.
/// </summary>
public RulesetInfo Ruleset { get; }
public UserProfileData(APIUser user)
public UserProfileData(APIUser user, RulesetInfo ruleset)
{
User = user;
Ruleset = ruleset;
}
}
}

View File

@ -23,10 +23,10 @@ namespace osu.Game.Overlays
/// <typeparam name="T">The type of item to be represented by tabs.</typeparam>
public abstract partial class TabControlOverlayHeader<T> : OverlayHeader, IHasCurrentValue<T>
{
protected OsuTabControl<T> TabControl;
protected OsuTabControl<T> TabControl { get; }
protected Container TabControlContainer { get; }
private readonly Box controlBackground;
private readonly Container tabControlContainer;
private readonly BindableWithCurrent<T> current = new BindableWithCurrent<T>();
public Bindable<T> Current
@ -41,7 +41,7 @@ namespace osu.Game.Overlays
set
{
base.ContentSidePadding = value;
tabControlContainer.Padding = new MarginPadding { Horizontal = value };
TabControlContainer.Padding = new MarginPadding { Horizontal = value };
}
}
@ -57,7 +57,7 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.Both,
},
tabControlContainer = new Container
TabControlContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,

View File

@ -5,16 +5,21 @@ using System;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Extensions;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@ -23,31 +28,47 @@ namespace osu.Game.Overlays
{
public partial class UserProfileOverlay : FullscreenOverlay<ProfileHeader>
{
protected override Container<Drawable> Content => onlineViewContainer;
private readonly OnlineViewContainer onlineViewContainer;
private readonly LoadingLayer loadingLayer;
private ProfileSection? lastSection;
private ProfileSection[]? sections;
private GetUserRequest? userReq;
private ProfileSectionsContainer? sectionsContainer;
private ProfileSectionTabControl? tabs;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
public const float CONTENT_X_MARGIN = 70;
public UserProfileOverlay()
: base(OverlayColourScheme.Pink)
{
base.Content.AddRange(new Drawable[]
{
onlineViewContainer = new OnlineViewContainer($"Sign in to view the {Header.Title.Title}")
{
RelativeSizeAxes = Axes.Both
},
loadingLayer = new LoadingLayer(true)
});
}
protected override ProfileHeader CreateHeader() => new ProfileHeader();
protected override Color4 BackgroundColour => ColourProvider.Background6;
public void ShowUser(IUser user)
public void ShowUser(IUser user, IRulesetInfo? ruleset = null)
{
if (user.OnlineID == APIUser.SYSTEM_USER_ID)
return;
Show();
if (user.OnlineID == Header?.User.Value?.User.Id)
if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true)
return;
if (sectionsContainer != null)
@ -116,16 +137,19 @@ namespace osu.Game.Overlays
sectionsContainer.ScrollToTop();
userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID) : new GetUserRequest(user.Username);
userReq.Success += userLoadComplete;
userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset);
userReq.Success += u => userLoadComplete(u, ruleset);
API.Queue(userReq);
loadingLayer.Show();
}
private void userLoadComplete(APIUser user)
private void userLoadComplete(APIUser user, IRulesetInfo? ruleset)
{
Debug.Assert(sections != null && sectionsContainer != null && tabs != null);
var userProfile = new UserProfileData(user);
var actualRuleset = rulesets.GetRuleset(ruleset?.ShortName ?? user.PlayMode).AsNonNull();
var userProfile = new UserProfileData(user, actualRuleset);
Header.User.Value = userProfile;
if (user.ProfileOrder != null)
@ -143,6 +167,8 @@ namespace osu.Game.Overlays
}
}
}
loadingLayer.Hide();
}
private partial class ProfileSectionTabControl : OverlayTabControl<ProfileSection>