mirror of
https://github.com/ppy/osu.git
synced 2024-11-14 16:37:26 +08:00
Merge pull request #28991 from Layendan/master
Add collection and favourite buttons to results screen
This commit is contained in:
commit
2876cb73bd
67
osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs
Normal file
67
osu.Game.Tests/Visual/Ranking/TestSceneCollectionButton.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneCollectionButton : OsuManualInputManagerTestScene
|
||||
{
|
||||
private CollectionButton? collectionButton;
|
||||
private readonly BeatmapInfo beatmapInfo = new BeatmapInfo { OnlineID = 88 };
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create button", () => Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = collectionButton = new CollectionButton(beatmapInfo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionButton()
|
||||
{
|
||||
AddStep("click collection button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(collectionButton!);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection popover is visible", () => this.ChildrenOfType<CollectionPopover>().Single().State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("click outside popover", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection popover is hidden", () => this.ChildrenOfType<CollectionPopover>().Single().State.Value == Visibility.Hidden);
|
||||
|
||||
AddStep("click collection button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(collectionButton!);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("press escape", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddAssert("collection popover is hidden", () => this.ChildrenOfType<CollectionPopover>().Single().State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
}
|
82
osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs
Normal file
82
osu.Game.Tests/Visual/Ranking/TestSceneFavouriteButton.cs
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Ranking;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneFavouriteButton : OsuTestScene
|
||||
{
|
||||
private FavouriteButton? favourite;
|
||||
|
||||
private readonly BeatmapSetInfo beatmapSetInfo = new BeatmapSetInfo { OnlineID = 88 };
|
||||
private readonly BeatmapSetInfo invalidBeatmapSetInfo = new BeatmapSetInfo();
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create button", () => Child = favourite = new FavouriteButton(beatmapSetInfo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
AddStep("register request handling", () => dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
if (!(request is GetBeatmapSetRequest beatmapSetRequest)) return false;
|
||||
|
||||
beatmapSetRequest.TriggerSuccess(new APIBeatmapSet
|
||||
{
|
||||
OnlineID = beatmapSetRequest.ID,
|
||||
HasFavourited = false,
|
||||
FavouriteCount = 0,
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoggedOutIn()
|
||||
{
|
||||
AddStep("log out", () => API.Logout());
|
||||
checkEnabled(false);
|
||||
AddStep("log in", () =>
|
||||
{
|
||||
API.Login("test", "test");
|
||||
((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh");
|
||||
});
|
||||
checkEnabled(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInvalidBeatmap()
|
||||
{
|
||||
AddStep("make beatmap invalid", () => Child = favourite = new FavouriteButton(invalidBeatmapSetInfo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
AddStep("log in", () =>
|
||||
{
|
||||
API.Login("test", "test");
|
||||
((DummyAPIAccess)API).AuthenticateSecondFactor("abcdefgh");
|
||||
});
|
||||
checkEnabled(false);
|
||||
}
|
||||
|
||||
private void checkEnabled(bool expected)
|
||||
{
|
||||
AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite!.Enabled.Value == expected);
|
||||
}
|
||||
}
|
||||
}
|
81
osu.Game/Screens/Ranking/CollectionButton.cs
Normal file
81
osu.Game/Screens/Ranking/CollectionButton.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class CollectionButton : GrayButton, IHasPopover
|
||||
{
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
private readonly Bindable<bool> isInAnyCollection;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realmAccess { get; set; } = null!;
|
||||
|
||||
private IDisposable? collectionSubscription;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public CollectionButton(BeatmapInfo beatmapInfo)
|
||||
: base(FontAwesome.Solid.Book)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
isInAnyCollection = new Bindable<bool>(false);
|
||||
|
||||
Size = new Vector2(75, 30);
|
||||
|
||||
TooltipText = "collections";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Action = this.ShowPopover;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
collectionSubscription = realmAccess.RegisterForNotifications(r => r.All<BeatmapCollection>(), collectionsChanged);
|
||||
|
||||
isInAnyCollection.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
collectionSubscription?.Dispose();
|
||||
}
|
||||
|
||||
private void collectionsChanged(IRealmCollection<BeatmapCollection> sender, ChangeSet? changes)
|
||||
{
|
||||
isInAnyCollection.Value = sender.AsEnumerable().Any(c => c.BeatmapMD5Hashes.Contains(beatmapInfo.MD5Hash));
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Background.FadeColour(isInAnyCollection.Value ? colours.Green : colours.Gray4, 500, Easing.InOutExpo);
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new CollectionPopover(beatmapInfo);
|
||||
}
|
||||
}
|
67
osu.Game/Screens/Ranking/CollectionPopover.cs
Normal file
67
osu.Game/Screens/Ranking/CollectionPopover.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class CollectionPopover : OsuPopover
|
||||
{
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
|
||||
|
||||
public CollectionPopover(BeatmapInfo beatmapInfo)
|
||||
: base(false)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
|
||||
Body.CornerRadius = 4;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new OsuMenu(Direction.Vertical, true)
|
||||
{
|
||||
Items = items,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnFocusLost(FocusLostEvent e)
|
||||
{
|
||||
base.OnFocusLost(e);
|
||||
Hide();
|
||||
}
|
||||
|
||||
private OsuMenuItem[] items
|
||||
{
|
||||
get
|
||||
{
|
||||
var collectionItems = realm.Realm.All<BeatmapCollection>()
|
||||
.OrderBy(c => c.Name)
|
||||
.AsEnumerable()
|
||||
.Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast<OsuMenuItem>().ToList();
|
||||
|
||||
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, () => manageCollectionsDialog?.Show()));
|
||||
|
||||
return collectionItems.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
osu.Game/Screens/Ranking/FavouriteButton.cs
Normal file
155
osu.Game/Screens/Ranking/FavouriteButton.cs
Normal file
@ -0,0 +1,155 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class FavouriteButton : GrayButton
|
||||
{
|
||||
public readonly BeatmapSetInfo BeatmapSetInfo;
|
||||
private APIBeatmapSet? beatmapSet;
|
||||
private readonly Bindable<BeatmapSetFavouriteState> current;
|
||||
|
||||
private PostBeatmapFavouriteRequest? favouriteRequest;
|
||||
private LoadingLayer loading = null!;
|
||||
|
||||
private readonly IBindable<APIUser> localUser = new Bindable<APIUser>();
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public FavouriteButton(BeatmapSetInfo beatmapSetInfo)
|
||||
: base(FontAwesome.Regular.Heart)
|
||||
{
|
||||
BeatmapSetInfo = beatmapSetInfo;
|
||||
current = new BindableWithCurrent<BeatmapSetFavouriteState>(new BeatmapSetFavouriteState(false, 0));
|
||||
|
||||
Size = new Vector2(75, 30);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Add(loading = new LoadingLayer(true, false));
|
||||
|
||||
Action = toggleFavouriteStatus;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
current.BindValueChanged(_ => updateState(), true);
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
localUser.BindValueChanged(_ => updateUser(), true);
|
||||
}
|
||||
|
||||
private void getBeatmapSet()
|
||||
{
|
||||
GetBeatmapSetRequest beatmapSetRequest = new GetBeatmapSetRequest(BeatmapSetInfo.OnlineID);
|
||||
|
||||
loading.Show();
|
||||
beatmapSetRequest.Success += beatmapSet =>
|
||||
{
|
||||
this.beatmapSet = beatmapSet;
|
||||
current.Value = new BeatmapSetFavouriteState(this.beatmapSet.HasFavourited, this.beatmapSet.FavouriteCount);
|
||||
|
||||
loading.Hide();
|
||||
Enabled.Value = true;
|
||||
};
|
||||
beatmapSetRequest.Failure += e =>
|
||||
{
|
||||
Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}");
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
loading.Hide();
|
||||
Enabled.Value = false;
|
||||
});
|
||||
};
|
||||
api.Queue(beatmapSetRequest);
|
||||
}
|
||||
|
||||
private void toggleFavouriteStatus()
|
||||
{
|
||||
if (beatmapSet == null)
|
||||
return;
|
||||
|
||||
Enabled.Value = false;
|
||||
loading.Show();
|
||||
|
||||
var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite;
|
||||
|
||||
favouriteRequest?.Cancel();
|
||||
favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType);
|
||||
|
||||
favouriteRequest.Success += () =>
|
||||
{
|
||||
bool favourited = actionType == BeatmapFavouriteAction.Favourite;
|
||||
|
||||
current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1));
|
||||
|
||||
Enabled.Value = true;
|
||||
loading.Hide();
|
||||
};
|
||||
favouriteRequest.Failure += e =>
|
||||
{
|
||||
Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}");
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
Enabled.Value = true;
|
||||
loading.Hide();
|
||||
});
|
||||
};
|
||||
|
||||
api.Queue(favouriteRequest);
|
||||
}
|
||||
|
||||
private void updateUser()
|
||||
{
|
||||
if (!(localUser.Value is GuestUser) && BeatmapSetInfo.OnlineID > 0)
|
||||
getBeatmapSet();
|
||||
else
|
||||
{
|
||||
Enabled.Value = false;
|
||||
current.Value = new BeatmapSetFavouriteState(false, 0);
|
||||
updateState();
|
||||
TooltipText = BeatmapsetsStrings.ShowDetailsFavouriteLogin;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (current.Value.Favourited)
|
||||
{
|
||||
Background.Colour = colours.Green;
|
||||
Icon.Icon = FontAwesome.Solid.Heart;
|
||||
TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite;
|
||||
}
|
||||
else
|
||||
{
|
||||
Background.Colour = colours.Gray4;
|
||||
Icon.Icon = FontAwesome.Regular.Heart;
|
||||
TooltipText = BeatmapsetsStrings.ShowDetailsFavourite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -76,7 +77,7 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user's personal statistics should be shown on the extended statistics panel
|
||||
/// after clicking the score panel associated with the <see cref="ResultsScreen.Score"/> being presented.
|
||||
/// after clicking the score panel associated with the <see cref="Score"/> being presented.
|
||||
/// Requires <see cref="Score"/> to be present.
|
||||
/// </summary>
|
||||
public bool ShowUserStatistics { get; init; }
|
||||
@ -97,7 +98,10 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
popInSample = audio.Samples.Get(@"UI/overlay-pop-in");
|
||||
|
||||
InternalChild = new GridContainer
|
||||
InternalChild = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
@ -155,7 +159,7 @@ namespace osu.Game.Screens.Ranking
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(5),
|
||||
Direction = FillDirection.Horizontal
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,6 +169,7 @@ namespace osu.Game.Screens.Ranking
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (Score != null)
|
||||
@ -202,6 +207,12 @@ namespace osu.Game.Screens.Ranking
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (Score?.BeatmapInfo != null)
|
||||
buttons.Add(new CollectionButton(Score.BeatmapInfo));
|
||||
|
||||
if (Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0)
|
||||
buttons.Add(new FavouriteButton(Score.BeatmapInfo.BeatmapSet));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -9,6 +9,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -200,7 +201,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
{
|
||||
var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId
|
||||
? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID)
|
||||
: beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID);
|
||||
: beatmapManager.QueryBeatmapSet(s => s.OnlineID == getBeatmapSetRequest.ID)?.PerformRead(s => s.Beatmaps.First().Detach());
|
||||
|
||||
if (baseBeatmap == null)
|
||||
{
|
||||
|
@ -306,7 +306,9 @@ namespace osu.Game.Tests.Visual
|
||||
StarRating = original.StarRating,
|
||||
DifficultyName = original.DifficultyName,
|
||||
}
|
||||
}
|
||||
},
|
||||
HasFavourited = false,
|
||||
FavouriteCount = 0,
|
||||
};
|
||||
|
||||
foreach (var beatmap in result.Beatmaps)
|
||||
|
Loading…
Reference in New Issue
Block a user