diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/TestCaseSocial.cs index d9325d4e54..d3ff18b37f 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/TestCaseSocial.cs @@ -1,13 +1,26 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Game.Overlays; +using osu.Game.Overlays.Social; using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseSocial : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserPanel), + typeof(SocialPanel), + typeof(FilterControl), + typeof(SocialOverlay), + typeof(SocialGridPanel), + typeof(SocialListPanel) + }; + public TestCaseSocial() { SocialOverlay s = new SocialOverlay diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs new file mode 100644 index 0000000000..5bc3d8e39e --- /dev/null +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class GetFriendsRequest : APIRequest> + { + protected override string Target => @"friends"; + } +} diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 45a02074b1..382b3fd0e7 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -22,7 +22,8 @@ namespace osu.Game.Overlays.Social public enum SocialSortCriteria { Rank, - //Location, + Name, + Location, //[Description("Time Zone")] //TimeZone, //[Description("World Map")] diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index 5107a76f0d..7bb4b4dde9 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -18,7 +18,8 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override float TabStripWidth => 438; - protected override SocialTab DefaultTab => SocialTab.OnlinePlayers; + + protected override SocialTab DefaultTab => SocialTab.AllPlayers; protected override FontAwesome Icon => FontAwesome.fa_users; protected override Drawable CreateHeaderText() @@ -53,12 +54,12 @@ namespace osu.Game.Overlays.Social public enum SocialTab { - [Description("Online Players")] - OnlinePlayers, - //[Description("Online Friends")] - //OnlineFriends, - //[Description("Online Team Members")] - //OnlineTeamMembers, + [Description("All Players")] + AllPlayers, + [Description("Friends")] + Friends, + //[Description("Team Members")] + //TeamMembers, //[Description("Chat Channels")] //ChatChannels, } diff --git a/osu.Game/Overlays/Social/SocialGridPanel.cs b/osu.Game/Overlays/Social/SocialGridPanel.cs new file mode 100644 index 0000000000..f9fbce123d --- /dev/null +++ b/osu.Game/Overlays/Social/SocialGridPanel.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialGridPanel : SocialPanel + { + public SocialGridPanel(User user) : base(user) + { + Width = 300; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialListPanel.cs b/osu.Game/Overlays/Social/SocialListPanel.cs new file mode 100644 index 0000000000..0f102005d6 --- /dev/null +++ b/osu.Game/Overlays/Social/SocialListPanel.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialListPanel : SocialPanel + { + public SocialListPanel(User user) : base(user) + { + RelativeSizeAxes = Axes.X; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs new file mode 100644 index 0000000000..54fb88f929 --- /dev/null +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialPanel : UserPanel + { + private const double hover_transition_time = 400; + + public SocialPanel(User user) : base(user) + { + } + + private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 2f, + Colour = Color4.Black.Opacity(0.25f), + }; + + private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 5f), + Radius = 10f, + Colour = Color4.Black.Opacity(0.3f), + }; + + protected override bool OnHover(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); + Content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); + Content.MoveToY(0, hover_transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200, Easing.Out); + } + } +} diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 3b17c7ae62..e61153d290 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -9,19 +9,22 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; +using osu.Framework.Configuration; +using osu.Framework.Threading; namespace osu.Game.Overlays { public class SocialOverlay : SearchableListOverlay, IOnlineComponent { - private readonly FillFlowContainer panelFlow; + private APIAccess api; + private readonly LoadingAnimation loading; + private FillFlowContainer panels; protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51"); @@ -31,27 +34,15 @@ namespace osu.Game.Overlays protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable users; - private readonly LoadingAnimation loading; - public IEnumerable Users { get { return users; } set { - if (users?.Equals(value) ?? false) return; - users = value; + if (users?.Equals(value) ?? false) + return; - if (users == null) - panelFlow.Clear(); - else - { - panelFlow.ChildrenEnumerable = users.Select(u => - { - var p = new UserPanel(u) { Width = 300 }; - p.Status.BindTo(u.Status); - return p; - }); - } + users = value?.ToList(); } } @@ -62,57 +53,153 @@ namespace osu.Game.Overlays ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); FourthWaveColour = OsuColour.FromHex(@"6d214d"); - ScrollFlow.Children = new[] + Add(loading = new LoadingAnimation()); + + Filter.Search.Current.ValueChanged += text => { - new OsuContextMenuContainer + if (!string.IsNullOrEmpty(text)) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = panelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 20 }, - Spacing = new Vector2(10f), - } - }, + // force searching in players until searching for friends is supported + Header.Tabs.Current.Value = SocialTab.AllPlayers; + + if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) + Filter.Tabs.Current.Value = SocialSortCriteria.Rank; + } }; - Add(loading = new LoadingAnimation()); + Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch); + + Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch); + + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch); + + currentQuery.ValueChanged += query => + { + queryChangedDebounce?.Cancel(); + + if (string.IsNullOrEmpty(query)) + Scheduler.AddOnce(updateSearch); + else + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); + }; + + currentQuery.BindTo(Filter.Search.Current); } [BackgroundDependencyLoader] private void load(APIAccess api) { - if (Users == null) - reloadUsers(api); + this.api = api; + api.Register(this); } - private void reloadUsers(APIAccess api) + private void recreatePanels(PanelDisplayStyle displayStyle) { - Users = null; + clearPanels(); - // no this is not the correct data source, but it's something. - var request = new GetUsersRequest(); - request.Success += res => + if (Users == null) + return; + + var newPanels = new FillFlowContainer { - Users = res.Select(e => e.User); - loading.Hide(); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10f), + Margin = new MarginPadding { Top = 10 }, + ChildrenEnumerable = Users.Select(u => + { + SocialPanel panel; + switch (displayStyle) + { + case PanelDisplayStyle.Grid: + panel = new SocialGridPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }; + break; + default: + panel = new SocialListPanel(u); + break; + } + panel.Status.BindTo(u.Status); + return panel; + }) }; - api.Queue(request); + LoadComponentAsync(newPanels, f => + { + if(panels != null) + ScrollFlow.Remove(panels); + + ScrollFlow.Add(panels = newPanels); + }); + } + + private APIRequest getUsersRequest; + + private readonly Bindable currentQuery = new Bindable(); + + private ScheduledDelegate queryChangedDebounce; + + private void updateSearch() + { + queryChangedDebounce?.Cancel(); + + if (!IsLoaded) + return; + + Users = null; + clearPanels(); + loading.Hide(); + getUsersRequest?.Cancel(); + + if (api?.IsLoggedIn == false) + return; + + switch (Header.Tabs.Current.Value) + { + case SocialTab.Friends: + var friendRequest = new GetFriendsRequest(); // TODO filter arguments? + friendRequest.Success += updateUsers; + api.Queue(getUsersRequest = friendRequest); + break; + default: + var userRequest = new GetUsersRequest(); // TODO filter arguments! + userRequest.Success += response => updateUsers(response.Select(r => r.User)); + api.Queue(getUsersRequest = userRequest); + break; + } loading.Show(); } + private void updateUsers(IEnumerable newUsers) + { + Users = newUsers; + loading.Hide(); + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); + } + + private void clearPanels() + { + if (panels != null) + { + panels.Expire(); + panels = null; + } + } + public void APIStateChanged(APIAccess api, APIState state) { switch (state) { case APIState.Online: - reloadUsers(api); + Scheduler.AddOnce(updateSearch); break; default: Users = null; + clearPanels(); break; } } @@ -120,7 +207,7 @@ namespace osu.Game.Overlays public enum SortDirection { - Descending, Ascending, + Descending } } diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index 60dd822d8c..2edd7cbf55 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -44,7 +44,7 @@ namespace osu.Game.Users new Avatar(user) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(200), + OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) ); } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index a57d55f7b7..8379e69869 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -39,10 +39,14 @@ namespace osu.Game.Users public string AvatarUrl; [JsonProperty(@"cover_url")] - public string CoverUrl; + public string CoverUrl + { + get { return Cover?.Url; } + set { Cover = new UserCover { Url = value }; } + } - //[JsonProperty(@"cover")] - //public UserCover Cover; + [JsonProperty(@"cover")] + public UserCover Cover; public class UserCover { diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 812b32912c..c62ba392b8 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -28,9 +28,12 @@ namespace osu.Game.Users private const float content_padding = 10; private const float status_height = 30; - private readonly Container statusBar; - private readonly Box statusBg; - private readonly OsuSpriteText statusMessage; + private Container statusBar; + private Box statusBg; + private OsuSpriteText statusMessage; + + private Container content; + protected override Container Content => content; public readonly Bindable Status = new Bindable(); @@ -45,133 +48,7 @@ namespace osu.Game.Users this.user = user; - FillFlowContainer infoContainer; - Height = height - status_height; - Masking = true; - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - - Children = new Drawable[] - { - new DelayedLoadWrapper(new UserCoverBackground(user) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), - }, 0) { RelativeSizeAxes = Axes.Both }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding }, - Children = new Drawable[] - { - new UpdateableAvatar - { - Size = new Vector2(height - status_height - content_padding * 2), - User = user, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = height - status_height - content_padding }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = user.Username, - TextSize = 18, - Font = @"Exo2.0-SemiBoldItalic", - }, - infoContainer = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.X, - Height = 20f, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - new DrawableFlag(user.Country) - { - Width = 30f, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - }, - }, - }, - }, - statusBar = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Alpha = 0f, - Children = new Drawable[] - { - statusBg = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, - Shadow = true, - Size = new Vector2(14), - }, - statusMessage = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-Semibold", - }, - }, - }, - }, - }, - }; - - if (user.IsSupporter) - infoContainer.Add(new SupporterIcon - { - RelativeSizeAxes = Axes.Y, - Width = 20f, - }); } [BackgroundDependencyLoader(permitNulls: true)] @@ -180,6 +57,139 @@ namespace osu.Game.Users if (colours == null) throw new ArgumentNullException(nameof(colours)); + FillFlowContainer infoContainer; + + AddInternal(content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }, + + Children = new Drawable[] + { + new DelayedLoadWrapper(new UserCoverBackground(user) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) + }, 300) { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = content_padding, Horizontal = content_padding }, + Children = new Drawable[] + { + new UpdateableAvatar + { + Size = new Vector2(height - status_height - content_padding * 2), + User = user, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = height - status_height - content_padding }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = user.Username, + TextSize = 18, + Font = @"Exo2.0-SemiBoldItalic", + }, + infoContainer = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.X, + Height = 20f, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), + Children = new Drawable[] + { + new DrawableFlag(user.Country) + { + Width = 30f, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + }, + }, + }, + statusBar = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Alpha = 0f, + Children = new Drawable[] + { + statusBg = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5f, 0f), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_circle_o, + Shadow = true, + Size = new Vector2(14), + }, + statusMessage = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = @"Exo2.0-Semibold", + }, + }, + }, + }, + }, + } + }); + + if (user.IsSupporter) + { + infoContainer.Add(new SupporterIcon + { + RelativeSizeAxes = Axes.Y, + Width = 20f, + }); + } + Status.ValueChanged += displayStatus; Status.ValueChanged += status => statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f87f664199..23cd435556 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -269,6 +269,7 @@ + @@ -314,6 +315,9 @@ + + +