From 0faa26fc13905d13f67cfef7da0fca420382f6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 Oct 2021 21:34:31 +0200 Subject: [PATCH 1/7] Add basic structure for buttons --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 34 ++++++++++++-- .../Cards/Buttons/BeatmapCardIconButton.cs | 46 +++++++++++++++++++ .../Drawables/Cards/Buttons/DownloadButton.cs | 22 +++++++++ .../Cards/Buttons/FavouriteButton.cs | 22 +++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index c53c1abd8d..a3650315cc 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -21,6 +22,7 @@ using osuTK; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; +using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -35,7 +37,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly APIBeatmapSet beatmapSet; private UpdateableOnlineBeatmapSetCover leftCover; - private FillFlowContainer iconArea; + private FillFlowContainer leftIconArea; + + private Container rightButtonArea; private Container mainContent; private BeatmapCardContentBackground mainContentBackground; @@ -79,7 +83,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSet }, - iconArea = new FillFlowContainer + leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), AutoSizeAxes = Axes.Both, @@ -88,6 +92,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, + rightButtonArea = new Container + { + Name = @"Right (button) area", + Width = 30, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 14), + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet), + new DownloadButton(beatmapSet) + } + } + }, mainContent = new Container { Name = @"Main content", @@ -226,10 +251,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; if (beatmapSet.HasVideo) - iconArea.Add(new IconPill(FontAwesome.Solid.Film)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film)); if (beatmapSet.HasStoryboard) - iconArea.Add(new IconPill(FontAwesome.Solid.Image)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image)); if (beatmapSet.HasExplicitContent) { @@ -306,6 +331,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs new file mode 100644 index 0000000000..155259d859 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public abstract class BeatmapCardIconButton : OsuHoverContainer + { + protected readonly SpriteIcon Icon; + + private float size; + + public new float Size + { + get => size; + set + { + size = value; + Icon.Size = new Vector2(size); + } + } + + protected BeatmapCardIconButton() + { + Add(Icon = new SpriteIcon()); + + AutoSizeAxes = Axes.Both; + Size = 12; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Anchor = Origin = Anchor.Centre; + + IdleColour = colourProvider.Light1; + HoverColour = colourProvider.Content1; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs new file mode 100644 index 0000000000..da6fe3c8be --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class DownloadButton : BeatmapCardIconButton + { + private readonly APIBeatmapSet beatmapSet; + + public DownloadButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Solid.FileDownload; + } + + // TODO: implement behaviour + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs new file mode 100644 index 0000000000..1859a66821 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class FavouriteButton : BeatmapCardIconButton + { + private readonly APIBeatmapSet beatmapSet; + + public FavouriteButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Regular.Heart; + } + + // TODO: implement behaviour + } +} From 5cb533004de0329b90c4428f4cf1311db51529d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Oct 2021 23:11:31 +0200 Subject: [PATCH 2/7] Add test coverage for favourite button --- .../TestSceneBeatmapCardFavouriteButton.cs | 86 +++++++++++++++++++ .../Requests/PostBeatmapFavouriteRequest.cs | 7 +- 2 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs new file mode 100644 index 0000000000..77c9debef6 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardFavouriteButton : OsuManualInputManagerTestScene + { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestInitialState([Values] bool favourited) + { + APIBeatmapSet beatmapSetInfo = null; + FavouriteButton button = null; + + AddStep("create beatmap set", () => + { + beatmapSetInfo = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSetInfo.HasFavourited = favourited; + }); + AddStep("create button", () => Child = button = new FavouriteButton(beatmapSetInfo) { Scale = new Vector2(2) }); + + assertCorrectIcon(favourited); + AddAssert("correct tooltip text", () => button.TooltipText == (favourited ? BeatmapsetsStrings.ShowDetailsUnfavourite : BeatmapsetsStrings.ShowDetailsFavourite)); + } + + [Test] + public void TestRequestHandling() + { + APIBeatmapSet beatmapSetInfo = null; + FavouriteButton button = null; + BeatmapFavouriteAction? lastRequestAction = null; + + AddStep("create beatmap set", () => beatmapSetInfo = CreateAPIBeatmapSet(Ruleset.Value)); + AddStep("create button", () => Child = button = new FavouriteButton(beatmapSetInfo) { Scale = new Vector2(2) }); + + assertCorrectIcon(false); + + AddStep("register request handling", () => dummyAPI.HandleRequest = request => + { + if (!(request is PostBeatmapFavouriteRequest favouriteRequest)) + return false; + + lastRequestAction = favouriteRequest.Action; + request.TriggerSuccess(); + return true; + }); + + AddStep("click icon", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("favourite request sent", () => lastRequestAction == BeatmapFavouriteAction.Favourite); + assertCorrectIcon(true); + + AddStep("click icon", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("unfavourite request sent", () => lastRequestAction == BeatmapFavouriteAction.UnFavourite); + assertCorrectIcon(false); + } + + private void assertCorrectIcon(bool favourited) => AddAssert("icon correct", + () => this.ChildrenOfType().Single().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart)); + } +} diff --git a/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs index f3724230cb..9fdc3382aa 100644 --- a/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs +++ b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs @@ -8,20 +8,21 @@ namespace osu.Game.Online.API.Requests { public class PostBeatmapFavouriteRequest : APIRequest { + public readonly BeatmapFavouriteAction Action; + private readonly int id; - private readonly BeatmapFavouriteAction action; public PostBeatmapFavouriteRequest(int id, BeatmapFavouriteAction action) { this.id = id; - this.action = action; + Action = action; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"action", action.ToString().ToLowerInvariant()); + req.AddParameter(@"action", Action.ToString().ToLowerInvariant()); return req; } From b5cbdcf9819dff87a57758b4577c80da49f9ed85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Oct 2021 23:36:27 +0200 Subject: [PATCH 3/7] Implement basic behaviour of favourite button --- .../Cards/Buttons/FavouriteButton.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 1859a66821..145b3f41b0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -1,8 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Logging; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { @@ -10,13 +15,54 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { private readonly APIBeatmapSet beatmapSet; + private PostBeatmapFavouriteRequest favouriteRequest; + public FavouriteButton(APIBeatmapSet beatmapSet) { this.beatmapSet = beatmapSet; - Icon.Icon = FontAwesome.Regular.Heart; + updateState(); } - // TODO: implement behaviour + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + Action = () => + { + var actionType = beatmapSet.HasFavourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + + Enabled.Value = false; + favouriteRequest.Success += () => + { + beatmapSet.HasFavourited = actionType == BeatmapFavouriteAction.Favourite; + Enabled.Value = true; + updateState(); + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Enabled.Value = true; + }; + + api.Queue(favouriteRequest); + }; + } + + private void updateState() + { + if (beatmapSet.HasFavourited) + { + Icon.Icon = FontAwesome.Solid.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; + } + else + { + Icon.Icon = FontAwesome.Regular.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsFavourite; + } + } } } From f4b8dee2d0562d931faf6a96f6326882f3190df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 Nov 2021 17:42:32 +0100 Subject: [PATCH 4/7] Update favourite statistic value on favourite button clicks --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 18 +++++++++ .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 7 +++- .../Cards/BeatmapSetFavouriteState.cs | 31 ++++++++++++++++ .../Cards/Buttons/FavouriteButton.cs | 37 ++++++++++++++----- .../Cards/Statistics/FavouritesStatistic.cs | 29 +++++++++++++-- 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 8bfee02310..0feaa8f480 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -9,8 +9,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osuTK; @@ -20,6 +23,8 @@ namespace osu.Game.Tests.Visual.Beatmaps { public class TestSceneBeatmapCard : OsuTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + private APIBeatmapSet[] testCases; #region Test case generation @@ -164,6 +169,19 @@ namespace osu.Game.Tests.Visual.Beatmaps #endregion + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => dummyAPI.HandleRequest = request => + { + if (!(request is PostBeatmapFavouriteRequest)) + return false; + + request.TriggerSuccess(); + return true; + }); + } + private Drawable createContent(OverlayColourScheme colourScheme, Func creationFunc) { var colourProvider = new OverlayColourProvider(colourScheme); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index a3650315cc..71376c28f1 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -35,6 +36,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private const float corner_radius = 10; private readonly APIBeatmapSet beatmapSet; + private readonly Bindable favouriteState; private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; @@ -55,6 +57,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards : base(HoverSampleSet.Submit) { this.beatmapSet = beatmapSet; + favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); } [BackgroundDependencyLoader] @@ -108,7 +111,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(0, 14), Children = new BeatmapCardIconButton[] { - new FavouriteButton(beatmapSet), + new FavouriteButton(beatmapSet) { Current = favouriteState }, new DownloadButton(beatmapSet) } } @@ -312,7 +315,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) yield return new NominationsStatistic(beatmapSet.NominationStatus); - yield return new FavouritesStatistic(beatmapSet); + yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; yield return new PlayCountStatistic(beatmapSet); var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs new file mode 100644 index 0000000000..82523cc865 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Beatmaps.Drawables.Cards.Statistics; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + /// + /// Stores the current favourite state of a beatmap set. + /// Used to coordinate between and . + /// + public readonly struct BeatmapSetFavouriteState + { + /// + /// Whether the currently logged-in user has favourited this beatmap. + /// + public bool Favourited { get; } + + /// + /// The number of favourites that the beatmap set has received, including the currently logged-in user. + /// + public int FavouriteCount { get; } + + public BeatmapSetFavouriteState(bool favourited, int favouriteCount) + { + Favourited = favourited; + FavouriteCount = favouriteCount; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 145b3f41b0..0b43b1c619 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Logging; using osu.Game.Online.API; @@ -11,17 +13,24 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public class FavouriteButton : BeatmapCardIconButton + public class FavouriteButton : BeatmapCardIconButton, IHasCurrentValue { - private readonly APIBeatmapSet beatmapSet; + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly int onlineBeatmapID; private PostBeatmapFavouriteRequest favouriteRequest; public FavouriteButton(APIBeatmapSet beatmapSet) { - this.beatmapSet = beatmapSet; - - updateState(); + current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + onlineBeatmapID = beatmapSet.OnlineID; } [BackgroundDependencyLoader] @@ -29,17 +38,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Action = () => { - var actionType = beatmapSet.HasFavourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); Enabled.Value = false; favouriteRequest.Success += () => { - beatmapSet.HasFavourited = actionType == BeatmapFavouriteAction.Favourite; + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + Enabled.Value = true; - updateState(); }; favouriteRequest.Failure += e => { @@ -51,9 +62,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons }; } + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + } + private void updateState() { - if (beatmapSet.HasFavourited) + if (current.Value.Favourited) { Icon.Icon = FontAwesome.Solid.Heart; TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs index 7b3286ddcc..d924fd938b 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Statistics @@ -11,13 +13,32 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// /// Shows the number of favourites that a beatmap set has received. /// - public class FavouritesStatistic : BeatmapCardStatistic + public class FavouritesStatistic : BeatmapCardStatistic, IHasCurrentValue { + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + public FavouritesStatistic(IBeatmapSetOnlineInfo onlineInfo) { - Icon = onlineInfo.HasFavourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; - Text = onlineInfo.FavouriteCount.ToMetric(decimals: 1); - TooltipText = BeatmapsStrings.PanelFavourites(onlineInfo.FavouriteCount.ToLocalisableString(@"N0")); + current = new BindableWithCurrent(new BeatmapSetFavouriteState(onlineInfo.HasFavourited, onlineInfo.FavouriteCount)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + Icon = current.Value.Favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; + Text = current.Value.FavouriteCount.ToMetric(decimals: 1); + TooltipText = BeatmapsStrings.PanelFavourites(current.Value.FavouriteCount.ToLocalisableString(@"N0")); } } } From 9a2425f316903a8725da40f6d0374afa91aebe8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 21:36:28 +0900 Subject: [PATCH 5/7] Remove unused field for now to appease inspectcode --- osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index da6fe3c8be..00c0ccc3ce 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -8,12 +8,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { - private readonly APIBeatmapSet beatmapSet; - public DownloadButton(APIBeatmapSet beatmapSet) { - this.beatmapSet = beatmapSet; - Icon.Icon = FontAwesome.Solid.FileDownload; } From 72489b32f922bc38f5306e5556411f32c5c582d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 21:39:16 +0900 Subject: [PATCH 6/7] Move toggle code into own method for readability --- .../Cards/Buttons/FavouriteButton.cs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 0b43b1c619..a61065b468 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -27,47 +27,48 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private PostBeatmapFavouriteRequest favouriteRequest; + [Resolved] + private IAPIProvider api { get; set; } + public FavouriteButton(APIBeatmapSet beatmapSet) { current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); onlineBeatmapID = beatmapSet.OnlineID; } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - Action = () => - { - var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; - - favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); - - Enabled.Value = false; - favouriteRequest.Success += () => - { - bool favourited = actionType == BeatmapFavouriteAction.Favourite; - - current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); - - Enabled.Value = true; - }; - favouriteRequest.Failure += e => - { - Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); - Enabled.Value = true; - }; - - api.Queue(favouriteRequest); - }; - } - protected override void LoadComplete() { base.LoadComplete(); + + Action = toggleFavouriteStatus; current.BindValueChanged(_ => updateState(), true); } + private void toggleFavouriteStatus() + { + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); + + Enabled.Value = false; + favouriteRequest.Success += () => + { + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + + Enabled.Value = true; + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Enabled.Value = true; + }; + + api.Queue(favouriteRequest); + } + private void updateState() { if (current.Value.Favourited) From 74603253d2522baeb5342a68ee970c387efdb69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 13:42:56 +0100 Subject: [PATCH 7/7] Store full model rather than online ID only --- .../Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index a61065b468..9fed2fde6f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons set => current.Current = value; } - private readonly int onlineBeatmapID; + private readonly APIBeatmapSet beatmapSet; private PostBeatmapFavouriteRequest favouriteRequest; @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons public FavouriteButton(APIBeatmapSet beatmapSet) { current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - onlineBeatmapID = beatmapSet.OnlineID; + this.beatmapSet = beatmapSet; } protected override void LoadComplete() @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); Enabled.Value = false; favouriteRequest.Success += () =>