diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs new file mode 100644 index 0000000000..205d869efe --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Users; +using osuTK; +using System; +using System.Collections.Generic; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneReplayDownloadButton : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ReplayDownloadButton) + }; + + private ReplayDownloadButton downloadButton; + + public TestSceneReplayDownloadButton() + { + Add(new ReplayDownloadButton(getScoreInfo()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + private ScoreInfo getScoreInfo() + { + return new APILegacyScoreInfo + { + ID = 1, + OnlineScoreID = 2553163309, + Ruleset = new OsuRuleset().RulesetInfo, + Replay = true, + User = new User + { + Id = 39828, + Username = @"WubWoofWolf", + } + }; + } + } +} diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 5eb2bb74bb..f0375335da 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -54,6 +54,12 @@ namespace osu.Game.Online attachDownload(download); }; + manager.DownloadFailed += download => + { + if (download.Model.Equals(Model.Value)) + attachDownload(null); + }; + manager.ItemAdded += itemAdded; manager.ItemRemoved += itemRemoved; } @@ -109,6 +115,9 @@ namespace osu.Game.Online if (!s.Equals(Model.Value)) return; + // when model states are being updated from manager, update the model being held by us also so that it will + // be up do date when being consumed for reading files etc. + Model.Value = s; State.Value = state; }); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 33be8c41ef..2d82987da0 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -64,5 +64,7 @@ namespace osu.Game.Scoring public ScoreInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); + + protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.OnlineScoreID == model.OnlineScoreID); } } diff --git a/osu.Game/Screens/Play/ReplayDownloadButton.cs b/osu.Game/Screens/Play/ReplayDownloadButton.cs index 51061e0660..f56246e615 100644 --- a/osu.Game/Screens/Play/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Play/ReplayDownloadButton.cs @@ -1,55 +1,147 @@ using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Containers; using osu.Game.Online; using osu.Game.Scoring; +using osuTK; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Effects; +using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Play { - public class ReplayDownloadButton : DownloadTrackingComposite + public class ReplayDownloadButton : DownloadTrackingComposite, IHasTooltip { - [Resolved] + private const int size = 40; + + [Resolved(canBeNull: true)] private OsuGame game { get; set; } [Resolved] private ScoreManager scores { get; set; } + [Resolved] + private OsuColour colours { get; set; } + + private OsuClickableContainer button; + private SpriteIcon downloadIcon; + private SpriteIcon playIcon; + private ShakeContainer shakeContainer; + + public string TooltipText + { + get + { + if (scores.IsAvailableLocally(Model.Value)) + return @"Watch replay"; + + if (Model.Value is APILegacyScoreInfo apiScore && apiScore.Replay) + return @"Download replay"; + + return @"Replay unavailable"; + } + } + public ReplayDownloadButton(ScoreInfo score) : base(score) { + Size = new Vector2(size); + CornerRadius = size / 2; + Masking = true; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private bool hasReplay => (Model.Value is APILegacyScoreInfo apiScore && apiScore.Replay) || scores.IsAvailableLocally(Model.Value); + + [BackgroundDependencyLoader(true)] + private void load() { - AddInternal(new TwoLayerButton + EdgeEffect = new EdgeEffectParameters { - BackgroundColour = colours.Yellow, - Icon = FontAwesome.Solid.PlayCircle, - Text = @"Replay", - HoverColour = colours.YellowDark, - Action = onReplay, - }); - } - - private void onReplay() - { - if (scores.IsAvailableLocally(Model.Value)) - { - game.PresentScore(Model.Value); - return; - } - - scores.Download(Model.Value); - - scores.ItemAdded += score => - { - if (score.Equals(Model.Value)) - // use the newly added score instead of ModelInfo.Score because that won't have the Files property populated - game.PresentScore(score); - //ReplayLoaded?.Invoke(scores.GetScore(score)); + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 5, }; + + InternalChild = shakeContainer = new ShakeContainer + { + RelativeSizeAxes = Axes.Both, + Child = button = new OsuClickableContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GrayF, + }, + playIcon = new SpriteIcon + { + Icon = FontAwesome.Solid.Play, + Size = Vector2.Zero, + Colour = colours.Gray3, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + downloadIcon = new SpriteIcon + { + Icon = FontAwesome.Solid.FileDownload, + Size = Vector2.Zero, + Colour = colours.Gray3, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }, + } + }; + + button.Action = () => + { + switch (State.Value) + { + case DownloadState.LocallyAvailable: + game.PresentScore(Model.Value); + break; + + case DownloadState.NotDownloaded: + scores.Download(Model.Value); + break; + + case DownloadState.Downloading: + shakeContainer.Shake(); + break; + } + }; + + State.BindValueChanged(state => + { + switch (state.NewValue) + { + case DownloadState.Downloading: + FadeEdgeEffectTo(colours.Yellow, 400, Easing.OutQuint); + button.Enabled.Value = false; + break; + + case DownloadState.LocallyAvailable: + playIcon.ResizeTo(13, 300, Easing.OutQuint); + downloadIcon.ResizeTo(Vector2.Zero, 300, Easing.OutExpo); + FadeEdgeEffectTo(Color4.Black.Opacity(0.4f), 400, Easing.OutQuint); + button.Enabled.Value = true; + break; + + case DownloadState.NotDownloaded: + playIcon.ResizeTo(Vector2.Zero, 300, Easing.OutQuint); + downloadIcon.ResizeTo(13, 300, Easing.OutExpo); + FadeEdgeEffectTo(Color4.Black.Opacity(0.4f), 400, Easing.OutQuint); + button.Enabled.Value = true; + break; + } + }, true); } } } diff --git a/osu.Game/Screens/Play/SoloResults.cs b/osu.Game/Screens/Play/SoloResults.cs index 5c747d2d31..e8f4a7e182 100644 --- a/osu.Game/Screens/Play/SoloResults.cs +++ b/osu.Game/Screens/Play/SoloResults.cs @@ -20,23 +20,6 @@ namespace osu.Game.Screens.Play { } - [BackgroundDependencyLoader] - private void load() - { - if (scores.IsAvailableLocally(Score) || hasOnlineReplay) - { - AddInternal(new ReplayDownloadButton(Score) - { - Anchor = Framework.Graphics.Anchor.BottomRight, - Origin = Framework.Graphics.Anchor.BottomRight, - Height = 80, - Width = 100, - }); - } - } - - private bool hasOnlineReplay => Score is APILegacyScoreInfo apiScore && apiScore.OnlineScoreID != null && apiScore.Replay; - protected override IEnumerable CreateResultPages() => new IResultPageInfo[] { new ScoreOverviewPageInfo(Score, Beatmap.Value), diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index a82156e34e..3068fd77e8 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -33,9 +33,12 @@ namespace osu.Game.Screens.Ranking.Pages private Container scoreContainer; private ScoreCounter scoreCounter; + private readonly ScoreInfo score; + public ScoreResultsPage(ScoreInfo score, WorkingBeatmap beatmap) : base(score, beatmap) { + this.score = score; } private FillFlowContainer statisticsContainer; @@ -163,9 +166,15 @@ namespace osu.Game.Screens.Ranking.Pages Direction = FillDirection.Horizontal, LayoutDuration = 200, LayoutEasing = Easing.OutQuint - } - } - } + }, + }, + }, + new ReplayDownloadButton(score) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = 10 }, + }, }; statisticsContainer.ChildrenEnumerable = Score.Statistics.OrderByDescending(p => p.Key).Select(s => new DrawableScoreStatistic(s));