diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 2146ea333a..cd382c2bb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingOverlay overlay; + private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); + [BackgroundDependencyLoader] private void load() { @@ -70,113 +72,123 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - - // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); - - // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); } [Test] - public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRankFilter(Scoring.ScoreRank.XH); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); + + // test supporter on Played filter + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + + // test supporter on both Rank Achieved and Played filter + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + } + + [Test] + public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); + } + [Test] + public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); } private void fetchFor(params BeatmapSetInfo[] beatmaps) @@ -185,44 +197,36 @@ namespace osu.Game.Tests.Visual.Online setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - overlay.ChildrenOfType().Single().Query.TriggerChange(); + searchControl.Query.TriggerChange(); } - private void toggleRandomRankFilter() + private void toggleRankFilter(Scoring.ScoreRank rank) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Rank Achieved filter", () => + AddStep("toggle Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + searchControl.Ranks.Clear(); + searchControl.Ranks.Add(rank); }); } - private void toggleRandomSupporterOnlyPlayedFilter() + private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + AddStep("toggle Played filter", () => searchControl.Played.Value = played); } - private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + private void supporterRequiredPlaceholderShown() { - if (supporterRequiredShown) - { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } - if (notFoundShown) - { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + private void notFoundPlaceholderShown() + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void noPlaceholderShown() + { + AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 6e83dc0bf4..f49d913bb2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Fired when a search finishes. Contains only new items in the case of pagination. /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action, BeatmapListingSearchControl> SearchFinished; + public Action SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +218,19 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(sets, searchControl); + List filters = new List(); + + if (searchControl.Played.Value != SearchPlayed.Any) + filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed); + + if (searchControl.Ranks.Any()) + filters.Add(BeatmapsStrings.ListingSearchFiltersRank); + + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); } else { - SearchFinished?.Invoke(sets, null); + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); } }; @@ -246,5 +256,30 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public enum SearchResultType + { + ResultsReturned, + SupporterOnlyFilter + } + + public struct SearchResult + { + public SearchResultType Type { get; private set; } + public List Results { get; private set; } + public List Filters { get; private set; } + + public static SearchResult ResultsReturned(List results) => new SearchResult + { + Type = SearchResultType.ResultsReturned, + Results = results + }; + + public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + { + Type = SearchResultType.SupporterOnlyFilter, + Filters = filters + }; + } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 578e70e630..63800e6585 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -119,28 +120,28 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) + private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) + // non-supporter user used supporter-only filters + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + { + supporterRequiredContent.UpdateText(searchResult.Filters); + addContentToPlaceholder(supporterRequiredContent); + return; + } + + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); - // non-supporter user used supporter-only filters - if (searchControl != null) - { - supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - if (filterControl.CurrentPage == 0) { //No matches case if (!newPanels.Any()) { - LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + addContentToPlaceholder(notFoundContent); return; } @@ -182,16 +183,11 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // not found display may be used multiple times, so don't expire/dispose it. + // the placeholder may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } - else if (lastContent == supporterRequiredContent) - { - // supporter required display may be used multiple times, so don't expire/dispose it. - transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); - } else { // Consider the case when the new content is smaller than the last content. @@ -260,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText supporterRequiredText; + private OsuSpriteText filtersText; public SupporterRequiredDrawable() { @@ -289,30 +285,20 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - supporterRequiredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16), - Colour = Colour4.White, - Margin = new MarginPadding { Bottom = 10 }, - }, - createSupporterTagLink(), + createSupporterText(), } }); } - public void UpdateText(bool playedFilter, bool rankFilter) + public void UpdateText(List filters) { - List filters = new List(); - if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + // use string literals for now + filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - private Drawable createSupporterTagLink() + private Drawable createSupporterText() { - LinkFlowContainer supporterTagLink = new LinkFlowContainer + LinkFlowContainer supporterRequiredText = new LinkFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -320,8 +306,18 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); - return supporterTagLink; + filtersText = (OsuSpriteText)supporterRequiredText.AddText( + "_", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ).First(); + + supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); + + return supporterRequiredText; } }