diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 77fe3be933..969bafa98b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -114,12 +114,12 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderStates() { - AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); - AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); + AddStep(@"Empty Scores", () => leaderboard.SetErrorState(LeaderboardErrorState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardErrorState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardErrorState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardErrorState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardErrorState.Unavailable)); + AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardErrorState.NoneSelected)); } private void showPersonalBestWithNullPosition() @@ -401,22 +401,9 @@ namespace osu.Game.Tests.Visual.SongSelect }; } - private void showBeatmapWithStatus(BeatmapOnlineStatus status) - { - leaderboard.BeatmapInfo = new BeatmapInfo - { - OnlineID = 1113057, - Status = status, - }; - } - private class FailableLeaderboard : BeatmapLeaderboard { - public void SetRetrievalState(PlaceholderState state) - { - Scores = null; - PlaceholderState = state; - } + public new void SetErrorState(LeaderboardErrorState errorState) => base.SetErrorState(errorState); public new ICollection Scores { diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 8cf20ea8d1..2e552f9774 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -53,6 +53,8 @@ namespace osu.Game.Online.Leaderboards private APIRequest fetchScoresRequest; + private LeaderboardErrorState errorState; + [Resolved(CanBeNull = true)] private IAPIProvider api { get; set; } @@ -169,14 +171,37 @@ namespace osu.Game.Online.Leaderboards RefetchScores(); } + /// + /// Perform a full refetch of scores using current criteria. + /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Reset the leaderboard into an empty state. + /// protected virtual void Reset() { cancelPendingWork(); Scores = null; } + /// + /// Call when a retrieval or display failure happened to show a relevant message to the user. + /// + /// The state to display. + protected void SetErrorState(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NoError: + throw new InvalidOperationException($"State {errorState} cannot be set by a leaderboard implementation."); + } + + Debug.Assert(scores?.Any() != true); + + setErrorState(errorState); + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -195,7 +220,7 @@ namespace osu.Game.Online.Leaderboards Reset(); - PlaceholderState = PlaceholderState.Retrieving; + setErrorState(LeaderboardErrorState.NoError); loading.Show(); currentFetchCancellationSource = new CancellationTokenSource(); @@ -210,7 +235,7 @@ namespace osu.Game.Online.Leaderboards if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested) return; - PlaceholderState = PlaceholderState.NetworkFailure; + SetErrorState(LeaderboardErrorState.NetworkFailure); }); api?.Queue(fetchScoresRequest); @@ -223,77 +248,6 @@ namespace osu.Game.Online.Leaderboards fetchScoresRequest?.Cancel(); } - #region Placeholder handling - - private Placeholder currentPlaceholder; - - private PlaceholderState placeholderState; - - /// - /// Update the placeholder visibility. - /// Setting this to anything other than PlaceholderState.Successful will cancel all existing retrieval requests and hide scores. - /// - protected PlaceholderState PlaceholderState - { - get => placeholderState; - set - { - if (value == placeholderState) - return; - - placeholderState = value; - - switch (placeholderState) - { - case PlaceholderState.NetworkFailure: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) - { - Action = RefetchScores - }); - break; - - case PlaceholderState.NoneSelected: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!")); - break; - - case PlaceholderState.Unavailable: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); - break; - - case PlaceholderState.NoScores: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"No records yet!")); - break; - - case PlaceholderState.NotLoggedIn: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!")); - break; - - case PlaceholderState.NotSupporter: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); - break; - - case PlaceholderState.Retrieving: - Debug.Assert(scores?.Any() != true); - replacePlaceholder(null); - break; - - case PlaceholderState.Successful: - Debug.Assert(scores?.Any() == true); - replacePlaceholder(null); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } - private void updateScoresDrawables() { currentScoresAsyncLoadCancellationSource?.Cancel(); @@ -305,13 +259,14 @@ namespace osu.Game.Online.Leaderboards if (scores?.Any() != true) { + SetErrorState(LeaderboardErrorState.NoScores); loading.Hide(); - PlaceholderState = PlaceholderState.NoScores; return; } // ensure placeholder is hidden when displaying scores - PlaceholderState = PlaceholderState.Successful; + setErrorState(LeaderboardErrorState.NoError); + loading.Show(); LoadComponentAsync(new FillFlowContainer { @@ -339,25 +294,61 @@ namespace osu.Game.Online.Leaderboards }, (currentScoresAsyncLoadCancellationSource = new CancellationTokenSource()).Token); } - private void replacePlaceholder(Placeholder placeholder) + #region Placeholder handling + + private Placeholder placeholder; + + private void setErrorState(LeaderboardErrorState errorState) { - if (placeholder != null && placeholder.Equals(currentPlaceholder)) + if (errorState == this.errorState) return; - currentPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + this.errorState = errorState; + + placeholder?.FadeOut(150, Easing.OutQuint).Expire(); + + placeholder = getPlaceholderFor(errorState); if (placeholder == null) - { - currentPlaceholder = null; return; - } placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + } - currentPlaceholder = placeholder; + private Placeholder getPlaceholderFor(LeaderboardErrorState errorState) + { + switch (errorState) + { + case LeaderboardErrorState.NetworkFailure: + return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + { + Action = RefetchScores + }; + + case LeaderboardErrorState.NoneSelected: + return new MessagePlaceholder(@"Please select a beatmap!"); + + case LeaderboardErrorState.Unavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + + case LeaderboardErrorState.NoScores: + return new MessagePlaceholder(@"No records yet!"); + + case LeaderboardErrorState.NotLoggedIn: + return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + + case LeaderboardErrorState.NotSupporter: + return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + + case LeaderboardErrorState.NoError: + return null; + + default: + throw new ArgumentOutOfRangeException(); + } } #endregion diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs similarity index 82% rename from osu.Game/Online/Leaderboards/PlaceholderState.cs rename to osu.Game/Online/Leaderboards/LeaderboardErrorState.cs index 297241fa73..17f47bb557 100644 --- a/osu.Game/Online/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardErrorState.cs @@ -3,10 +3,9 @@ namespace osu.Game.Online.Leaderboards { - public enum PlaceholderState + public enum LeaderboardErrorState { - Successful, - Retrieving, + NoError, NetworkFailure, Unavailable, NoneSelected, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 31868624bb..b0ba830076 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -33,19 +33,12 @@ namespace osu.Game.Screens.Select.Leaderboards set { if (beatmapInfo == null && value == null) - { - // always null scores to ensure a correct initial display. - // see weird `scoresLoadedOnce` logic in base implementation. - Scores = null; return; - } if (beatmapInfo?.Equals(value) == true) return; beatmapInfo = value; - Scores = null; - RefetchScores(); } } @@ -114,7 +107,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (fetchBeatmapInfo == null) { - PlaceholderState = PlaceholderState.NoneSelected; + SetErrorState(LeaderboardErrorState.NoneSelected); return null; } @@ -126,19 +119,19 @@ namespace osu.Game.Screens.Select.Leaderboards if (api?.IsLoggedIn != true) { - PlaceholderState = PlaceholderState.NotLoggedIn; + SetErrorState(LeaderboardErrorState.NotLoggedIn); return null; } if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - PlaceholderState = PlaceholderState.Unavailable; + SetErrorState(LeaderboardErrorState.Unavailable); return null; } if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { - PlaceholderState = PlaceholderState.NotSupporter; + SetErrorState(LeaderboardErrorState.NotSupporter); return null; }