diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendsLayout.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendsLayout.cs new file mode 100644 index 0000000000..0e9fafb1b6 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendsLayout.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Dashboard.Friends; +using osu.Framework.Graphics; +using osu.Game.Users; +using osu.Game.Overlays; +using osu.Framework.Allocation; +using NUnit.Framework; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneFriendsLayout : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(FriendsLayout), + typeof(FriendsOnlineStatusControl), + typeof(UserListToolbar) + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private FriendsLayout layout; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = layout = new FriendsLayout() + }; + }); + + [Test] + public void TestPopulate() + { + AddStep("Populate", () => layout.Users = getUsers()); + } + + private List getUsers() => new List + { + new APIFriend + { + Username = @"flyte", + Id = 3103765, + IsOnline = true, + CurrentModeRank = 1111, + Country = new Country { FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }, + new APIFriend + { + Username = @"peppy", + Id = 2, + IsOnline = false, + CurrentModeRank = 2222, + Country = new Country { FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = true, + SupportLevel = 3, + }, + new APIFriend + { + Username = @"Evast", + Id = 8195163, + Country = new Country { FlagName = @"BY" }, + CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", + IsOnline = false, + LastVisit = DateTimeOffset.Now + } + }; + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs index 0d841dfef1..8bdf3c5dc1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs @@ -7,9 +7,9 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Overlays.Home.Friends; -using osu.Game.Users; +using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Tests.Visual.UserInterface { @@ -39,17 +39,17 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void Populate() { - AddStep("Populate", () => control.Populate(new List + AddStep("Populate", () => control.Populate(new List { - new User + new APIFriend { IsOnline = true }, - new User + new APIFriend { IsOnline = false }, - new User + new APIFriend { IsOnline = false } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs index 02b8839922..1546972580 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Overlays.Home.Friends; +using osu.Game.Overlays.Dashboard.Friends; using osuTK; namespace osu.Game.Tests.Visual.UserInterface diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs index 46890aa889..321f675aae 100644 --- a/osu.Game/Online/API/Requests/GetFriendsRequest.cs +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Game.Users; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetFriendsRequest : APIRequest> + public class GetFriendsRequest : APIRequest> { protected override string Target => @"friends"; } diff --git a/osu.Game/Online/API/Requests/Responses/APIFriend.cs b/osu.Game/Online/API/Requests/Responses/APIFriend.cs new file mode 100644 index 0000000000..91fed28d44 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIFriend.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIFriend : User + { + [JsonProperty(@"current_mode_rank")] + public int? CurrentModeRank; + } +} diff --git a/osu.Game/Overlays/Home/Friends/FriendsBundle.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsBundle.cs similarity index 55% rename from osu.Game/Overlays/Home/Friends/FriendsBundle.cs rename to osu.Game/Overlays/Dashboard/Friends/FriendsBundle.cs index 75d00dfef8..0062c49c91 100644 --- a/osu.Game/Overlays/Home/Friends/FriendsBundle.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsBundle.cs @@ -1,18 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -namespace osu.Game.Overlays.Home.Friends +using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Dashboard.Friends { public class FriendsBundle { public FriendsOnlineStatus Status { get; } - public int Count { get; } + public int Count => Users.Count; - public FriendsBundle(FriendsOnlineStatus status, int count) + public List Users { get; } + + public FriendsBundle(FriendsOnlineStatus status, List users) { Status = status; - Count = count; + Users = users; } } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendsLayout.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsLayout.cs new file mode 100644 index 0000000000..55f394cb78 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsLayout.cs @@ -0,0 +1,256 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +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.Users; +using osuTK; + +namespace osu.Game.Overlays.Dashboard.Friends +{ + public class FriendsLayout : CompositeDrawable + { + private List users = new List(); + + public List Users + { + get => users; + set + { + users = value; + + usersLoaded = true; + + onlineStatusControl.Populate(value); + } + } + + [Resolved] + private IAPIProvider api { get; set; } + + private GetFriendsRequest request; + private CancellationTokenSource cancellationToken; + + private Drawable currentContent; + + private readonly Box background; + private readonly Box controlBackground; + private readonly FriendsOnlineStatusControl onlineStatusControl; + private readonly UserListToolbar userListToolbar; + private readonly Container itemsPlaceholder; + private readonly LoadingLayer loading; + + public FriendsLayout() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + controlBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Top = 20, + Horizontal = 45 + }, + Child = onlineStatusControl = new FriendsOnlineStatusControl(), + } + } + }, + new Container + { + Name = "User List", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 40, + Vertical = 20 + }, + Child = userListToolbar = new UserListToolbar + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + itemsPlaceholder = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 50 } + }, + loading = new LoadingLayer(itemsPlaceholder) + } + } + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background4; + controlBackground.Colour = colourProvider.Background5; + } + + private bool usersLoaded; + + protected override void LoadComplete() + { + base.LoadComplete(); + + onlineStatusControl.Current.BindValueChanged(_ => recreatePanels()); + userListToolbar.DisplayStyle.BindValueChanged(_ => recreatePanels()); + userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels()); + + if (!api.IsLoggedIn) + return; + + request = new GetFriendsRequest(); + request.Success += response => Schedule(() => Users = response); + api.Queue(request); + } + + private void recreatePanels() + { + // Don't allow any changes until we have users loaded + if (!usersLoaded) + return; + + cancellationToken?.Cancel(); + + if (itemsPlaceholder.Any()) + loading.Show(); + + var groupedUsers = onlineStatusControl.Current.Value?.Users ?? new List(); + + var sortedUsers = sortUsers(groupedUsers); + + LoadComponentAsync(createTable(sortedUsers), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + } + + private void addContentToPlaceholder(Drawable content) + { + loading.Hide(); + + var lastContent = currentContent; + + if (lastContent != null) + { + lastContent.FadeOut(100, Easing.OutQuint).Expire(); + lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); + } + + itemsPlaceholder.Add(currentContent = content); + currentContent.FadeIn(200, Easing.OutQuint); + } + + private FillFlowContainer createTable(List users) + { + var style = userListToolbar.DisplayStyle.Value; + + return new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2), + Children = users.Select(u => createUserPanel(u, style)).ToList() + }; + } + + private UserPanel createUserPanel(User user, OverlayPanelDisplayStyle style) + { + switch (style) + { + default: + case OverlayPanelDisplayStyle.Card: + return new UserGridPanel(user).With(panel => + { + panel.Anchor = Anchor.TopCentre; + panel.Origin = Anchor.TopCentre; + panel.Width = 290; + }); + + case OverlayPanelDisplayStyle.List: + return new UserListPanel(user); + } + } + + private List sortUsers(List unsorted) + { + switch (userListToolbar.SortCriteria.Value) + { + default: + case UserSortCriteria.LastVisit: + return unsorted.OrderBy(u => u.LastVisit).Reverse().ToList(); + + case UserSortCriteria.Rank: + return unsorted.Where(u => u.CurrentModeRank.HasValue).OrderBy(u => u.CurrentModeRank).Concat(unsorted.Where(u => u.CurrentModeRank == null)).ToList(); + + case UserSortCriteria.Username: + return unsorted.OrderBy(u => u.Username).ToList(); + } + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + cancellationToken?.Cancel(); + + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusControl.cs similarity index 70% rename from osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.cs rename to osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusControl.cs index 196f01ab4a..2b716f228d 100644 --- a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusControl.cs @@ -3,22 +3,21 @@ using System.Collections.Generic; using System.Linq; -using osu.Game.Users; +using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Overlays.Home.Friends +namespace osu.Game.Overlays.Dashboard.Friends { public class FriendsOnlineStatusControl : OverlayStreamControl { protected override OverlayStreamItem CreateStreamItem(FriendsBundle value) => new FriendsOnlineStatusItem(value); - public void Populate(List users) + public void Populate(List users) { - var userCount = users.Count; - var onlineUsersCount = users.Count(user => user.IsOnline); + Clear(); - AddItem(new FriendsBundle(FriendsOnlineStatus.All, userCount)); - AddItem(new FriendsBundle(FriendsOnlineStatus.Online, onlineUsersCount)); - AddItem(new FriendsBundle(FriendsOnlineStatus.Offline, userCount - onlineUsersCount)); + AddItem(new FriendsBundle(FriendsOnlineStatus.All, users)); + AddItem(new FriendsBundle(FriendsOnlineStatus.Online, users.Where(u => u.IsOnline).ToList())); + AddItem(new FriendsBundle(FriendsOnlineStatus.Offline, users.Where(u => !u.IsOnline).ToList())); Current.Value = Items.FirstOrDefault(); } diff --git a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs similarity index 96% rename from osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs rename to osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs index d9b780ce46..eada9420ea 100644 --- a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs @@ -5,7 +5,7 @@ using System; using osu.Game.Graphics; using osuTK.Graphics; -namespace osu.Game.Overlays.Home.Friends +namespace osu.Game.Overlays.Dashboard.Friends { public class FriendsOnlineStatusItem : OverlayStreamItem { diff --git a/osu.Game/Overlays/Home/Friends/UserListToolbar.cs b/osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs similarity index 96% rename from osu.Game/Overlays/Home/Friends/UserListToolbar.cs rename to osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs index f7c5e9f4fd..fb4b938183 100644 --- a/osu.Game/Overlays/Home/Friends/UserListToolbar.cs +++ b/osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Bindables; -namespace osu.Game.Overlays.Home.Friends +namespace osu.Game.Overlays.Dashboard.Friends { public class UserListToolbar : CompositeDrawable { diff --git a/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs similarity index 90% rename from osu.Game/Overlays/Home/Friends/UserSortTabControl.cs rename to osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs index 2479fa4638..3a5f65212d 100644 --- a/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs +++ b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Overlays.Home.Friends +namespace osu.Game.Overlays.Dashboard.Friends { public class UserSortTabControl : OverlaySortTabControl { diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 02f7c9b0d3..ba572b0e78 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays { case SocialTab.Friends: var friendRequest = new GetFriendsRequest(); // TODO filter arguments? - friendRequest.Success += users => Users = users.ToArray(); + friendRequest.Success += users => Users = users.Select(u => (User)u).ToArray(); API.Queue(getUsersRequest = friendRequest); break;