Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Overlays/Dashboard/Friends/FriendsList.cs
2272 views
// Copyright (c) ppy Pty Ltd <[email protected]>. 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Users;
using osuTK;

namespace osu.Game.Overlays.Dashboard.Friends
{
    public partial class FriendsList : CompositeDrawable
    {
        public readonly IBindable<OnlineStatus> OnlineStream = new Bindable<OnlineStatus>();
        public readonly IBindable<UserSortCriteria> SortCriteria = new Bindable<UserSortCriteria>();
        public readonly IBindable<string> SearchText = new Bindable<string>();

        [Resolved]
        private MetadataClient metadataClient { get; set; } = null!;

        private readonly IBindableDictionary<int, UserPresence> friendPresences = new BindableDictionary<int, UserPresence>();
        private readonly OverlayPanelDisplayStyle style;
        private readonly APIUser[] friends;

        private FriendsSearchContainer searchContainer = null!;

        public FriendsList(OverlayPanelDisplayStyle style, APIUser[] friends)
        {
            this.style = style;
            this.friends = friends;

            RelativeSizeAxes = Axes.X;
            AutoSizeAxes = Axes.Y;
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            InternalChild = searchContainer = new FriendsSearchContainer
            {
                RelativeSizeAxes = Axes.X,
                AutoSizeAxes = Axes.Y,
                Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2),
                SortCriteria = { BindTarget = SortCriteria },
                ChildrenEnumerable = friends.Select(createUserPanel)
            };
        }

        protected override void LoadComplete()
        {
            base.LoadComplete();

            friendPresences.BindTo(metadataClient.FriendPresences);
            friendPresences.BindCollectionChanged(onFriendPresencesChanged);

            SearchText.BindValueChanged(onSearchTextChanged, true);
            OnlineStream.BindValueChanged(onFriendsStreamChanged, true);
        }

        private void onFriendPresencesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e)
        {
            switch (e.Action)
            {
                case NotifyDictionaryChangedAction.Add:
                case NotifyDictionaryChangedAction.Remove:
                    updatePanelVisibilities();
                    break;
            }
        }

        private void onSearchTextChanged(ValueChangedEvent<string> search)
        {
            searchContainer.SearchTerm = search.NewValue;
        }

        private void onFriendsStreamChanged(ValueChangedEvent<OnlineStatus> stream)
        {
            updatePanelVisibilities();
        }

        private void updatePanelVisibilities()
        {
            foreach (var panel in searchContainer)
            {
                switch (OnlineStream.Value)
                {
                    case OnlineStatus.All:
                        panel.CanBeShown.Value = true;
                        break;

                    case OnlineStatus.Online:
                        panel.CanBeShown.Value = friendPresences.ContainsKey(panel.User.OnlineID);
                        break;

                    case OnlineStatus.Offline:
                        panel.CanBeShown.Value = !friendPresences.ContainsKey(panel.User.OnlineID);
                        break;
                }
            }
        }

        private FilterableUserPanel createUserPanel(APIUser user)
        {
            UserPanel panel;

            switch (style)
            {
                default:
                case OverlayPanelDisplayStyle.Card:
                    panel = new UserGridPanel(user);
                    panel.Anchor = Anchor.TopCentre;
                    panel.Origin = Anchor.TopCentre;
                    panel.Width = 290;
                    break;

                case OverlayPanelDisplayStyle.List:
                    panel = new UserListPanel(user);
                    break;

                case OverlayPanelDisplayStyle.Brick:
                    panel = new UserBrickPanel(user);
                    break;
            }

            return new FilterableUserPanel(panel);
        }

        private partial class FriendsSearchContainer : SearchContainer<FilterableUserPanel>
        {
            public readonly IBindable<UserSortCriteria> SortCriteria = new Bindable<UserSortCriteria>();

            protected override void LoadComplete()
            {
                base.LoadComplete();
                SortCriteria.BindValueChanged(_ => InvalidateLayout(), true);
            }

            public override IEnumerable<Drawable> FlowingChildren
            {
                get
                {
                    IEnumerable<FilterableUserPanel> panels = base.FlowingChildren.OfType<FilterableUserPanel>();

                    switch (SortCriteria.Value)
                    {
                        default:
                        case UserSortCriteria.LastVisit:
                            // Todo: Last visit time is not currently updated according to realtime user presence.
                            return panels.OrderByDescending(panel => panel.User.LastVisit);

                        case UserSortCriteria.Rank:
                            // Todo: Statistics are not currently updated according to realtime user statistics, but it's also not currently displayed in the panels.
                            return panels.OrderByDescending(panel => panel.User.Statistics.GlobalRank.HasValue).ThenBy(panel => panel.User.Statistics.GlobalRank ?? 0);

                        case UserSortCriteria.Username:
                            return panels.OrderBy(panel => panel.User.Username);
                    }
                }
            }
        }

        public partial class FilterableUserPanel : CompositeDrawable, IConditionalFilterable
        {
            public readonly Bindable<bool> CanBeShown = new Bindable<bool>();

            public APIUser User => panel.User;

            private readonly UserPanel panel;

            public FilterableUserPanel(UserPanel panel)
            {
                this.panel = panel;

                Anchor = panel.Anchor;
                Origin = panel.Origin;
                RelativeSizeAxes = panel.RelativeSizeAxes;
                AutoSizeAxes = panel.AutoSizeAxes;

                if (!AutoSizeAxes.HasFlagFast(Axes.X))
                    Width = panel.Width;

                if (!AutoSizeAxes.HasFlagFast(Axes.Y))
                    Height = panel.Height;

                InternalChild = panel;
            }

            IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;

            IEnumerable<LocalisableString> IHasFilterTerms.FilterTerms => panel.FilterTerms;

            bool IFilterable.MatchingFilter
            {
                set
                {
                    if (value)
                        Show();
                    else
                        Hide();
                }
            }

            bool IFilterable.FilteringActive
            {
                set { }
            }
        }
    }
}