diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 8bfee0231006..0feaa8f480b4 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -9,8 +9,11 @@ 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 @@ private static APIBeatmapSet getManyDifficultiesBeatmapSet(int count) #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 a3650315ccb9..71376c28f1cd 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 @@ public class BeatmapCard : OsuClickableContainer private const float corner_radius = 10; private readonly APIBeatmapSet beatmapSet; + private readonly Bindable favouriteState; private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; @@ -55,6 +57,7 @@ public BeatmapCard(APIBeatmapSet beatmapSet) : base(HoverSampleSet.Submit) { this.beatmapSet = beatmapSet; + favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); } [BackgroundDependencyLoader] @@ -108,7 +111,7 @@ private void load() Spacing = new Vector2(0, 14), Children = new BeatmapCardIconButton[] { - new FavouriteButton(beatmapSet), + new FavouriteButton(beatmapSet) { Current = favouriteState }, new DownloadButton(beatmapSet) } } @@ -312,7 +315,7 @@ private IEnumerable createStatistics() 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 000000000000..82523cc865d5 --- /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 145b3f41b0d1..0b43b1c6190c 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 @@ 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 @@ private void load(IAPIProvider api) { 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 @@ private void load(IAPIProvider api) }; } + 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 7b3286ddcc65..d924fd938b29 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")); } } }