1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 05:32:54 +08:00

Merge pull request #13598 from JimmyC7834/supporter-only-filter-content

Add supporter required placeholder in beatmap listing overlay
This commit is contained in:
Dan Balasescu 2021-06-28 10:27:56 +09:00 committed by GitHub
commit 9c0840268a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 316 additions and 9 deletions

View File

@ -1,6 +1,7 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -14,6 +15,8 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
@ -23,6 +26,8 @@ namespace osu.Game.Tests.Visual.Online
private BeatmapListingOverlay overlay;
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single();
[BackgroundDependencyLoader]
private void load()
{
@ -39,6 +44,16 @@ namespace osu.Game.Tests.Visual.Online
return true;
};
AddStep("initialize dummy", () =>
{
// non-supporter user
((DummyAPIAccess)API).LocalUser.Value = new User
{
Username = "TestBot",
Id = API.LocalUser.Value.Id + 1,
};
});
}
[Test]
@ -58,13 +73,164 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
}
[Test]
public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults()
{
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
notFoundPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
// both RankAchieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
}
[Test]
public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults()
{
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
notFoundPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
notFoundPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
notFoundPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
notFoundPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
}
[Test]
public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults()
{
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
noPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
}
[Test]
public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults()
{
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
noPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
noPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
noPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
noPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
}
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
overlay.ChildrenOfType<BeatmapListingSearchControl>().Single().Query.TriggerChange();
searchControl.Query.TriggerChange();
}
private void setRankAchievedFilter(ScoreRank[] ranks)
{
AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () =>
{
searchControl.Ranks.Clear();
searchControl.Ranks.AddRange(ranks);
});
}
private void setPlayedFilter(SearchPlayed played)
{
AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played);
}
private void supporterRequiredPlaceholderShown()
{
AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().SingleOrDefault()?.IsPresent == true);
}
private void notFoundPlaceholderShown()
{
AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
}
private void noPlaceholderShown()
{
AddUntilStep("no placeholder shown", () =>
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any()
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
}
private class TestAPIBeatmapSet : APIBeatmapSet

View File

@ -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;
@ -23,9 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing
public class BeatmapListingFilterControl : CompositeDrawable
{
/// <summary>
/// Fired when a search finishes. Contains only new items in the case of pagination.
/// Fired when a search finishes.
/// </summary>
public Action<List<BeatmapSetInfo>> SearchFinished;
public Action<SearchResult> SearchFinished;
/// <summary>
/// Fired when search criteria change.
@ -212,7 +214,25 @@ namespace osu.Game.Overlays.BeatmapListing
lastResponse = response;
getSetsRequest = null;
SearchFinished?.Invoke(sets);
// check if a non-supporter used supporter-only filters
if (!api.LocalUser.Value.IsSupporter)
{
List<LocalisableString> filters = new List<LocalisableString>();
if (searchControl.Played.Value != SearchPlayed.Any)
filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed);
if (searchControl.Ranks.Any())
filters.Add(BeatmapsStrings.ListingSearchFiltersRank);
if (filters.Any())
{
SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters));
return;
}
}
SearchFinished?.Invoke(SearchResult.ResultsReturned(sets));
};
api.Queue(getSetsRequest);
@ -237,5 +257,53 @@ namespace osu.Game.Overlays.BeatmapListing
base.Dispose(isDisposing);
}
/// <summary>
/// Indicates the type of result of a user-requested beatmap search.
/// </summary>
public enum SearchResultType
{
/// <summary>
/// Actual results have been returned from API.
/// </summary>
ResultsReturned,
/// <summary>
/// The user is not a supporter, but used supporter-only search filters.
/// </summary>
SupporterOnlyFilters
}
/// <summary>
/// Describes the result of a user-requested beatmap search.
/// </summary>
public struct SearchResult
{
public SearchResultType Type { get; private set; }
/// <summary>
/// Contains the beatmap sets returned from API.
/// Valid for read if and only if <see cref="Type"/> is <see cref="SearchResultType.ResultsReturned"/>.
/// </summary>
public List<BeatmapSetInfo> Results { get; private set; }
/// <summary>
/// Contains the names of supporter-only filters requested by the user.
/// Valid for read if and only if <see cref="Type"/> is <see cref="SearchResultType.SupporterOnlyFilters"/>.
/// </summary>
public List<LocalisableString> SupporterOnlyFiltersUsed { get; private set; }
public static SearchResult ResultsReturned(List<BeatmapSetInfo> results) => new SearchResult
{
Type = SearchResultType.ResultsReturned,
Results = results
};
public static SearchResult SupporterOnlyFilters(List<LocalisableString> filters) => new SearchResult
{
Type = SearchResultType.SupporterOnlyFilters,
SupporterOnlyFiltersUsed = filters
};
}
}
}

View File

@ -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;
@ -15,7 +16,9 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
@ -33,6 +36,7 @@ namespace osu.Game.Overlays
private Container panelTarget;
private FillFlowContainer<BeatmapPanel> foundContent;
private NotFoundDrawable notFoundContent;
private SupporterRequiredDrawable supporterRequiredContent;
private BeatmapListingFilterControl filterControl;
public BeatmapListingOverlay()
@ -76,6 +80,7 @@ namespace osu.Game.Overlays
{
foundContent = new FillFlowContainer<BeatmapPanel>(),
notFoundContent = new NotFoundDrawable(),
supporterRequiredContent = new SupporterRequiredDrawable(),
}
}
},
@ -115,9 +120,16 @@ namespace osu.Game.Overlays
private Task panelLoadDelegate;
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult)
{
var newPanels = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters)
{
supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed);
addContentToPlaceholder(supporterRequiredContent);
return;
}
var newPanels = searchResult.Results.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@ -128,7 +140,7 @@ namespace osu.Game.Overlays
//No matches case
if (!newPanels.Any())
{
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
addContentToPlaceholder(notFoundContent);
return;
}
@ -170,9 +182,9 @@ 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 placeholders may be used multiple times, so don't expire/dispose them.
transform.Schedule(() => panelTarget.Remove(lastContent));
}
else
@ -240,6 +252,67 @@ namespace osu.Game.Overlays
}
}
// TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside
// (https://github.com/ppy/osu-framework/issues/4530)
public class SupporterRequiredDrawable : CompositeDrawable
{
private LinkFlowContainer supporterRequiredText;
public SupporterRequiredDrawable()
{
RelativeSizeAxes = Axes.X;
Height = 225;
Alpha = 0;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
AddInternal(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Texture = textures.Get(@"Online/supporter-required"),
},
supporterRequiredText = new LinkFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Bottom = 10 },
},
}
});
}
public void UpdateText(List<LocalisableString> filters)
{
supporterRequiredText.Clear();
supporterRequiredText.AddText(
BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(),
t =>
{
t.Font = OsuFont.GetFont(size: 16);
t.Colour = Colour4.White;
}
);
supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag");
}
}
private const double time_between_fetches = 500;
private double lastFetchDisplayedTime;