1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 18:52:55 +08:00

Combine pagination logic into BeatmapListingFilterControl

This commit is contained in:
Dean Herbert 2020-05-14 15:35:11 +09:00
parent 04c9973526
commit c836c9319b
3 changed files with 111 additions and 164 deletions

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -12,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@ -20,9 +22,34 @@ namespace osu.Game.Overlays.BeatmapListing
{
public class BeatmapListingFilterControl : CompositeDrawable
{
/// <summary>
/// Fired when a search finishes. Contains only new items in the case of pagination.
/// </summary>
public Action<List<BeatmapSetInfo>> SearchFinished;
/// <summary>
/// Fired when search criteria change.
/// </summary>
public Action SearchStarted;
private List<BeatmapSetInfo> currentBeatmaps;
/// <summary>
/// True when pagination has reached the end of available results.
/// </summary>
private bool noMoreResults;
/// <summary>
/// The current page fetched of results (zero index).
/// </summary>
public int CurrentPage { get; private set; }
private readonly BeatmapListingSearchControl searchControl;
private readonly BeatmapListingSortTabControl sortControl;
private readonly Box sortControlBackground;
private ScheduledDelegate queryChangedDebounce;
private SearchBeatmapSetsRequest getSetsRequest;
private SearchBeatmapSetsResponse lastResponse;
[Resolved]
private IAPIProvider api { get; set; }
@ -30,19 +57,11 @@ namespace osu.Game.Overlays.BeatmapListing
[Resolved]
private RulesetStore rulesets { get; set; }
private readonly BeatmapListingSearchControl searchControl;
private readonly BeatmapListingSortTabControl sortControl;
private readonly Box sortControlBackground;
private BeatmapListingPager beatmapListingPager;
private ScheduledDelegate queryChangedDebounce;
private ScheduledDelegate queryPagingDebounce;
public BeatmapListingFilterControl()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@ -118,69 +137,80 @@ namespace osu.Game.Overlays.BeatmapListing
public void TakeFocus() => searchControl.TakeFocus();
public void ShowMore()
/// <summary>
/// Fetch the next page of results. May result in a no-op if a fetch is already in progress, or if there are no results left.
/// </summary>
public void FetchNextPage()
{
if (beatmapListingPager == null || !beatmapListingPager.CanFetchNextPage)
// there may be no results left.
if (noMoreResults)
return;
if (queryPagingDebounce != null)
// there may already be an active request.
if (getSetsRequest != null)
return;
beatmapListingPager.FetchNextPage();
if (lastResponse != null)
CurrentPage++;
performRequest();
}
private void queueUpdateSearch(bool queryTextChanged = false)
{
SearchStarted?.Invoke();
cancelSearch();
resetSearch();
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
queryChangedDebounce = Scheduler.AddDelayed(() =>
{
resetSearch();
FetchNextPage();
}, queryTextChanged ? 500 : 100);
}
private void updateSearch()
private void performRequest()
{
cancelSearch();
beatmapListingPager = new BeatmapListingPager(
api,
rulesets,
getSetsRequest = new SearchBeatmapSetsRequest(
searchControl.Query.Value,
searchControl.Ruleset.Value,
lastResponse?.Cursor,
searchControl.Category.Value,
sortControl.Current.Value,
sortControl.SortDirection.Value
);
sortControl.SortDirection.Value);
beatmapListingPager.PageFetched += onSearchFinished;
getSetsRequest.Success += response =>
{
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
ShowMore();
if (sets.Count == 0)
noMoreResults = true;
lastResponse = response;
getSetsRequest = null;
SearchFinished?.Invoke(sets);
};
api.Queue(getSetsRequest);
}
private void cancelSearch()
private void resetSearch()
{
beatmapListingPager?.Reset();
noMoreResults = false;
CurrentPage = 0;
lastResponse = null;
getSetsRequest?.Cancel();
getSetsRequest = null;
queryChangedDebounce?.Cancel();
queryPagingDebounce?.Cancel();
queryPagingDebounce = null;
}
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
{
queryPagingDebounce = Scheduler.AddDelayed(() => queryPagingDebounce = null, 1000);
if (currentBeatmaps == null || !beatmapListingPager.IsPastFirstPage)
currentBeatmaps = beatmaps;
else
currentBeatmaps.AddRange(beatmaps);
SearchFinished?.Invoke(beatmaps);
}
protected override void Dispose(bool isDisposing)
{
cancelSearch();
resetSearch();
base.Dispose(isDisposing);
}

View File

@ -1,88 +0,0 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.BeatmapListing
{
public class BeatmapListingPager
{
private readonly IAPIProvider api;
private readonly RulesetStore rulesets;
private readonly string query;
private readonly RulesetInfo ruleset;
private readonly SearchCategory searchCategory;
private readonly SortCriteria sortCriteria;
private readonly SortDirection sortDirection;
public event Action<List<BeatmapSetInfo>> PageFetched;
private SearchBeatmapSetsRequest getSetsRequest;
private SearchBeatmapSetsResponse lastResponse;
private bool isLastPageFetched;
private bool isFetching => getSetsRequest != null;
public bool IsPastFirstPage { get; private set; }
public bool CanFetchNextPage => !isLastPageFetched && !isFetching;
public BeatmapListingPager(IAPIProvider api, RulesetStore rulesets, string query, RulesetInfo ruleset, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
{
this.api = api;
this.rulesets = rulesets;
this.query = query;
this.ruleset = ruleset;
this.searchCategory = searchCategory;
this.sortCriteria = sortCriteria;
this.sortDirection = sortDirection;
}
public void FetchNextPage()
{
if (isFetching)
return;
if (lastResponse != null)
IsPastFirstPage = true;
getSetsRequest = new SearchBeatmapSetsRequest(
query,
ruleset,
lastResponse?.Cursor,
searchCategory,
sortCriteria,
sortDirection);
getSetsRequest.Success += response =>
{
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
if (sets.Count == 0)
isLastPageFetched = true;
lastResponse = response;
getSetsRequest = null;
PageFetched?.Invoke(sets);
};
api.Queue(getSetsRequest);
}
public void Reset()
{
isLastPageFetched = false;
IsPastFirstPage = false;
lastResponse = null;
getSetsRequest?.Cancel();
getSetsRequest = null;
}
}
}

View File

@ -4,7 +4,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -34,8 +36,6 @@ namespace osu.Game.Overlays
private NotFoundDrawable notFoundContent;
private OverlayScrollContainer resultScrollContainer;
private const int pagination_scroll_distance = 500;
private bool shouldAddNextPage => resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance);
public BeatmapListingOverlay()
: base(OverlayColourScheme.Blue)
@ -121,51 +121,45 @@ namespace osu.Game.Overlays
loadingLayer.Show();
}
private Task panelLoadDelegate;
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
{
//No matches case
if (!beatmaps.Any())
var newPanels = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
{
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
return;
}
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
});
//New query case
if (!shouldAddNextPage)
if (filterControl.CurrentPage == 0)
{
//Spawn new child
var newPanels = new FillFlowContainer<BeatmapPanel>
//No matches case
if (!newPanels.Any())
{
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
return;
}
// spawn new children with the contained so we only clear old content at the last moment.
var content = new FillFlowContainer<BeatmapPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Alpha = 0,
Margin = new MarginPadding { Vertical = 15 },
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
})
ChildrenEnumerable = newPanels
};
foundContent = newPanels;
LoadComponentAsync(foundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
panelLoadDelegate = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
}
//Pagination case
else
{
beatmaps.ForEach(x =>
panelLoadDelegate = LoadComponentsAsync(newPanels, loaded =>
{
LoadComponentAsync(new GridBeatmapPanel(x)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}, loaded =>
{
foundContent.Add(loaded);
loaded.FadeIn(200, Easing.OutQuint);
});
lastFetchDisplayedTime = Time.Current;
foundContent.AddRange(loaded);
loaded.ForEach(p => p.FadeIn(200, Easing.OutQuint));
});
}
}
@ -173,6 +167,7 @@ namespace osu.Game.Overlays
private void addContentToPlaceholder(Drawable content)
{
loadingLayer.Hide();
lastFetchDisplayedTime = Time.Current;
var lastContent = currentContent;
@ -242,12 +237,22 @@ namespace osu.Game.Overlays
}
}
private const double time_between_fetches = 500;
private double lastFetchDisplayedTime;
protected override void Update()
{
base.Update();
if (shouldAddNextPage)
filterControl.ShowMore();
const int pagination_scroll_distance = 500;
bool shouldShowMore = panelLoadDelegate?.IsCompleted != false
&& Time.Current - lastFetchDisplayedTime > time_between_fetches
&& (resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance));
if (shouldShowMore)
filterControl.FetchNextPage();
}
}
}