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:
commit
9c0840268a
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user