< Summary

Class:FriendsTabComponentView
Assembly:FriendsHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/FriendsHUD/Scripts/Tabs/FriendsTabComponentView.cs
Covered lines:94
Uncovered lines:144
Coverable lines:238
Total lines:520
Line coverage:39.4% (94 of 238)
Covered branches:0
Total branches:0
Covered methods:23
Total methods:45
Method coverage:51.1% (23 of 45)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
FriendsTabComponentView()0%110100%
Initialize(...)0%110100%
OnEnable()0%110100%
OnDisable()0%110100%
Show()0%2100%
Hide()0%2100%
Update()0%220100%
Clear()0%110100%
Remove(...)0%20400%
Get(...)0%12300%
Populate(...)0%12300%
Set(...)0%56700%
RefreshControl()0%3.333066.67%
DisableSearchMode()0%2100%
EnableSearchMode()0%2100%
Enqueue(...)0%2100%
HideMoreFriendsToLoadHint()0%110100%
ShowMoreFriendsToLoadHint(...)0%2100%
ShowMoreFriendsLoadingSpinner()0%2100%
HideMoreFriendsLoadingSpinner()0%110100%
HandleSearchInputChanged(...)0%6200%
SetQueuedEntries()0%12.194020%
FetchProfilePicturesForVisibleEntries()0%110100%
FetchProfilePicturesForVisibleEntries(...)0%6.65060%
UpdateEmptyOrFilledState()0%110100%
CreateEntry(...)0%2100%
OnEntryWhisperClick(...)0%6200%
OnEntryMenuToggle(...)0%2100%
GetEntryPool()0%6200%
UpdateCounterLabel()0%110100%
HandleFriendBlockRequest(...)0%6200%
HandleUnfriendRequest(...)0%6200%
UpdateLayout()0%110100%
SortDirtyLists()0%5.264057.14%
RequestMoreEntries(...)0%32.097020%
ClearSearchResults()0%2.092071.43%
ClearOnlineAndOfflineFriends()0%2.062075%
WaitThenRequireMoreEntries()0%20400%
Show()0%110100%
Hide()0%2100%
FlagAsPendingToSort()0%2100%
Sort()0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/FriendsHUD/Scripts/Tabs/FriendsTabComponentView.cs

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using DCL;
 6using DCL.Helpers;
 7using DCl.Social.Friends;
 8using DCL.Social.Friends;
 9using SocialFeaturesAnalytics;
 10using TMPro;
 11using UnityEngine;
 12using UnityEngine.UI;
 13
 14public class FriendsTabComponentView : BaseComponentView
 15{
 16    private const int CREATION_AMOUNT_PER_FRAME = 5;
 17    private const int AVATAR_SNAPSHOTS_PER_FRAME = 5;
 18    private const int PRE_INSTANTIATED_ENTRIES = 25;
 19    private const string FRIEND_ENTRIES_POOL_NAME_PREFIX = "FriendEntriesPool_";
 20    private const float REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD = 0.005f;
 21    private const float MIN_TIME_TO_REQUIRE_MORE_ENTRIES = 2f;
 22
 23    [SerializeField] private GameObject enabledHeader;
 24    [SerializeField] private GameObject disabledHeader;
 25    [SerializeField] private GameObject emptyStateContainer;
 26    [SerializeField] private GameObject filledStateContainer;
 27    [SerializeField] private FriendEntry entryPrefab;
 28    [SerializeField] private FriendListComponents onlineFriendsList;
 29    [SerializeField] private FriendListComponents offlineFriendsList;
 30    [SerializeField] private FriendListComponents allFriendsList;
 31    [SerializeField] private FriendListComponents searchResultsFriendList;
 32    [SerializeField] private SearchBarComponentView searchBar;
 33    [SerializeField] private UserContextMenu contextMenuPanel;
 34    [SerializeField] private Model model;
 35    [SerializeField] private RectTransform viewport;
 36    [SerializeField] internal ScrollRect scroll;
 37
 38    [Header("Load More Entries")] [SerializeField]
 39    internal GameObject loadMoreEntriesContainer;
 40
 41    [SerializeField] internal TMP_Text loadMoreEntriesLabel;
 42    [SerializeField] internal GameObject loadMoreEntriesSpinner;
 43
 244    private readonly Dictionary<string, FriendEntryModel> creationQueue =
 45        new Dictionary<string, FriendEntryModel>();
 46
 247    private readonly Dictionary<string, PoolableObject> pooledEntries = new Dictionary<string, PoolableObject>();
 248    private readonly Dictionary<string, PoolableObject> searchPooledEntries = new Dictionary<string, PoolableObject>();
 49    private Pool entryPool;
 50    private bool isLayoutDirty;
 51    private IChatController chatController;
 52    private IFriendsController friendsController;
 53    private ISocialAnalytics socialAnalytics;
 54    private bool isSearchMode;
 255    private Vector2 lastScrollPosition = Vector2.one;
 56    private Coroutine requireMoreEntriesRoutine;
 57    private float loadMoreEntriesRestrictionTime;
 58
 159    public int Count => onlineFriendsList.list.Count()
 60                        + offlineFriendsList.list.Count()
 61                        + creationQueue.Keys.Count(s =>
 062                            !onlineFriendsList.list.Contains(s) && !offlineFriendsList.list.Contains(s));
 63
 64    public event Action<string> OnSearchRequested;
 65
 66    public event Action<FriendEntryModel> OnWhisper;
 67    public event Action OnRequireMoreEntries;
 68
 69    public void Initialize(IChatController chatController,
 70        IFriendsController friendsController,
 71        ISocialAnalytics socialAnalytics)
 72    {
 173        this.chatController = chatController;
 174        this.friendsController = friendsController;
 175        this.socialAnalytics = socialAnalytics;
 176    }
 77
 78    public override void OnEnable()
 79    {
 180        base.OnEnable();
 81
 182        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search friend"});
 183        searchBar.OnSearchText += HandleSearchInputChanged;
 184        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 185        contextMenuPanel.OnUnfriend += HandleUnfriendRequest;
 186        scroll.onValueChanged.AddListener(RequestMoreEntries);
 87
 88        int SortByAlphabeticalOrder(FriendEntryBase u1, FriendEntryBase u2)
 89        {
 090            return string.Compare(u1.Model.userName, u2.Model.userName, StringComparison.InvariantCultureIgnoreCase);
 91        }
 92
 193        onlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 194        offlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 195        searchResultsFriendList.list.SortingMethod = SortByAlphabeticalOrder;
 196        UpdateLayout();
 197        UpdateEmptyOrFilledState();
 98
 99        //TODO temporary, remove this and allFriendsList gameobjects later
 1100        allFriendsList.list.gameObject.SetActive(false);
 1101        allFriendsList.headerContainer.gameObject.SetActive(false);
 1102    }
 103
 104    public override void OnDisable()
 105    {
 1106        base.OnDisable();
 107
 1108        searchBar.OnSearchText -= HandleSearchInputChanged;
 1109        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 1110        contextMenuPanel.OnUnfriend -= HandleUnfriendRequest;
 1111        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 1112    }
 113
 114    public void Show()
 115    {
 0116        gameObject.SetActive(true);
 0117        enabledHeader.SetActive(true);
 0118        disabledHeader.SetActive(false);
 0119        searchBar.ClearSearch();
 0120    }
 121
 122    public void Hide()
 123    {
 0124        gameObject.SetActive(false);
 0125        enabledHeader.SetActive(false);
 0126        disabledHeader.SetActive(true);
 0127        contextMenuPanel.Hide();
 0128    }
 129
 130    public void Update()
 131    {
 126132        if (isLayoutDirty)
 1133            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 126134        isLayoutDirty = false;
 135
 126136        SortDirtyLists();
 126137        FetchProfilePicturesForVisibleEntries();
 126138        SetQueuedEntries();
 126139    }
 140
 141    public void Clear()
 142    {
 1143        HideMoreFriendsLoadingSpinner();
 1144        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1145        scroll.verticalNormalizedPosition = 1f;
 1146        creationQueue.Clear();
 147
 1148        searchResultsFriendList.list.Clear();
 149
 1150        ClearSearchResults();
 1151        ClearOnlineAndOfflineFriends();
 152
 1153        UpdateEmptyOrFilledState();
 1154        UpdateCounterLabel();
 1155    }
 156
 157    public void Remove(string userId)
 158    {
 0159        if (creationQueue.ContainsKey(userId))
 0160            creationQueue.Remove(userId);
 161
 0162        if (pooledEntries.TryGetValue(userId, out var pooledObject))
 163        {
 0164            entryPool.Release(pooledObject);
 0165            pooledEntries.Remove(userId);
 166        }
 167
 0168        if (searchPooledEntries.TryGetValue(userId, out var searchPooledObject))
 169        {
 0170            entryPool.Release(searchPooledObject);
 0171            searchPooledEntries.Remove(userId);
 172        }
 173
 0174        offlineFriendsList.list.Remove(userId);
 0175        onlineFriendsList.list.Remove(userId);
 0176        searchResultsFriendList.list.Remove(userId);
 177
 0178        UpdateEmptyOrFilledState();
 0179        UpdateCounterLabel();
 0180        UpdateLayout();
 0181    }
 182
 183    public FriendEntry Get(string userId)
 184    {
 0185        return (onlineFriendsList.list.Get(userId)
 186                ?? offlineFriendsList.list.Get(userId)
 187                ?? searchResultsFriendList.list.Get(userId)) as FriendEntry;
 188    }
 189
 190    private void Populate(string userId, FriendEntryModel model, FriendEntryBase entry)
 191    {
 0192        entry.Populate(model);
 193
 0194        if (isSearchMode)
 195        {
 0196            searchResultsFriendList.list.Remove(userId);
 0197            searchResultsFriendList.list.Add(userId, entry);
 0198            searchResultsFriendList.FlagAsPendingToSort();
 0199            searchResultsFriendList.list.Filter(searchBar.Text);
 200        }
 201        else
 202        {
 0203            if (model.status == PresenceStatus.ONLINE)
 204            {
 0205                offlineFriendsList.list.Remove(userId);
 0206                onlineFriendsList.list.Add(userId, entry);
 0207                onlineFriendsList.FlagAsPendingToSort();
 208            }
 209            else
 210            {
 0211                onlineFriendsList.list.Remove(userId);
 0212                offlineFriendsList.list.Add(userId, entry);
 0213                offlineFriendsList.FlagAsPendingToSort();
 214            }
 215        }
 216
 0217        UpdateLayout();
 0218        UpdateEmptyOrFilledState();
 0219        UpdateCounterLabel();
 0220    }
 221
 222    private void Set(string userId, FriendEntryModel model)
 223    {
 0224        if (creationQueue.ContainsKey(userId))
 225        {
 0226            creationQueue[userId] = model;
 0227            return;
 228        }
 229
 230        FriendEntryBase entry;
 231
 0232        if (isSearchMode)
 233        {
 0234            if (!searchResultsFriendList.list.Contains(userId))
 0235                entry = CreateEntry(userId, searchPooledEntries);
 236            else
 0237                entry = searchResultsFriendList.list.Get(userId);
 238        }
 239        else
 240        {
 0241            if (!onlineFriendsList.list.Contains(userId)
 242                && !offlineFriendsList.list.Contains(userId))
 0243                entry = CreateEntry(userId, pooledEntries);
 244            else
 0245                entry = onlineFriendsList.list.Get(userId) ?? offlineFriendsList.list.Get(userId);
 246        }
 247
 0248        Populate(userId, model, entry);
 0249    }
 250
 251    public override void RefreshControl()
 252    {
 1253        onlineFriendsList.Show();
 1254        offlineFriendsList.Show();
 255
 1256        if (model.isOnlineFriendsExpanded)
 1257            onlineFriendsList.list.Expand();
 258        else
 0259            onlineFriendsList.list.Collapse();
 260
 1261        if (model.isOfflineFriendsExpanded)
 1262            offlineFriendsList.list.Expand();
 263        else
 0264            offlineFriendsList.list.Collapse();
 0265    }
 266
 267    public void DisableSearchMode()
 268    {
 0269        isSearchMode = false;
 270
 0271        ClearSearchResults();
 272
 0273        searchBar.ClearSearch(false);
 0274        searchResultsFriendList.list.Clear();
 0275        searchResultsFriendList.Hide();
 0276        offlineFriendsList.Show();
 0277        onlineFriendsList.Show();
 0278    }
 279
 280    public void EnableSearchMode()
 281    {
 0282        isSearchMode = true;
 283
 0284        ClearSearchResults();
 285
 0286        offlineFriendsList.Hide();
 0287        onlineFriendsList.Hide();
 0288        searchResultsFriendList.Show();
 289
 0290        UpdateCounterLabel();
 0291        HideMoreFriendsToLoadHint();
 0292        UpdateLayout();
 0293    }
 294
 0295    public void Enqueue(string userId, FriendEntryModel model) => creationQueue[userId] = model;
 296
 297    public void HideMoreFriendsToLoadHint()
 298    {
 1299        loadMoreEntriesContainer.SetActive(false);
 1300        UpdateLayout();
 1301    }
 302
 303    public void ShowMoreFriendsToLoadHint(int hiddenCount)
 304    {
 0305        loadMoreEntriesLabel.text =
 306            $"{hiddenCount} friends hidden. Use the search bar to find them or scroll down to show more.";
 0307        loadMoreEntriesContainer.SetActive(true);
 0308        UpdateLayout();
 0309    }
 310
 0311    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 312
 1313    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 314
 0315    private void HandleSearchInputChanged(string search) => OnSearchRequested?.Invoke(search);
 316
 317    private void SetQueuedEntries()
 318    {
 252319        if (creationQueue.Count == 0) return;
 320
 0321        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 322        {
 0323            var pair = creationQueue.FirstOrDefault();
 0324            creationQueue.Remove(pair.Key);
 0325            Set(pair.Key, pair.Value);
 326        }
 327
 0328        HideMoreFriendsLoadingSpinner();
 0329    }
 330
 331    private void FetchProfilePicturesForVisibleEntries()
 332    {
 126333        FetchProfilePicturesForVisibleEntries(onlineFriendsList);
 126334        FetchProfilePicturesForVisibleEntries(offlineFriendsList);
 126335        FetchProfilePicturesForVisibleEntries(searchResultsFriendList);
 126336    }
 337
 338    private void FetchProfilePicturesForVisibleEntries(FriendListComponents friendListComponents)
 339    {
 756340        foreach (var entry in friendListComponents.list.Entries.Values
 341                     .Skip(friendListComponents.currentAvatarSnapshotIndex)
 342                     .Take(AVATAR_SNAPSHOTS_PER_FRAME))
 343        {
 0344            if (entry.IsVisible(viewport))
 0345                entry.EnableAvatarSnapshotFetching();
 346            else
 0347                entry.DisableAvatarSnapshotFetching();
 348        }
 349
 378350        friendListComponents.currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 351
 378352        if (friendListComponents.currentAvatarSnapshotIndex >= friendListComponents.list.Count())
 378353            friendListComponents.currentAvatarSnapshotIndex = 0;
 378354    }
 355
 356    private void UpdateEmptyOrFilledState()
 357    {
 2358        var totalFriends = onlineFriendsList.list.Count() + offlineFriendsList.list.Count();
 2359        emptyStateContainer.SetActive(totalFriends == 0);
 2360        filledStateContainer.SetActive(totalFriends > 0);
 2361    }
 362
 363    private FriendEntry CreateEntry(string userId, Dictionary<string, PoolableObject> poolableObjects)
 364    {
 0365        entryPool = GetEntryPool();
 0366        var newFriendEntry = entryPool.Get();
 0367        poolableObjects.Add(userId, newFriendEntry);
 0368        var entry = newFriendEntry.gameObject.GetComponent<FriendEntry>();
 0369        entry.Initialize(chatController, friendsController, socialAnalytics);
 370
 0371        entry.OnMenuToggle -= OnEntryMenuToggle;
 0372        entry.OnMenuToggle += OnEntryMenuToggle;
 0373        entry.OnWhisperClick -= OnEntryWhisperClick;
 0374        entry.OnWhisperClick += OnEntryWhisperClick;
 375
 0376        return entry;
 377    }
 378
 0379    private void OnEntryWhisperClick(FriendEntry friendEntry) => OnWhisper?.Invoke(friendEntry.Model);
 380
 381    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 382    {
 0383        friendEntry.Dock(contextMenuPanel);
 0384        contextMenuPanel.Show(friendEntry.Model.userId);
 0385    }
 386
 387    private Pool GetEntryPool()
 388    {
 0389        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 0390        if (entryPool != null) return entryPool;
 391
 0392        entryPool = PoolManager.i.AddPool(
 393            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 394            Instantiate(entryPrefab).gameObject,
 395            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 396            isPersistent: true);
 0397        entryPool.ForcePrewarm();
 398
 0399        return entryPool;
 400    }
 401
 402    private void UpdateCounterLabel()
 403    {
 1404        onlineFriendsList.countText.SetText("ONLINE ({0})", onlineFriendsList.list.Count());
 1405        offlineFriendsList.countText.SetText("OFFLINE ({0})", offlineFriendsList.list.Count());
 1406        searchResultsFriendList.countText.SetText("Results ({0})", searchResultsFriendList.list.Count());
 1407    }
 408
 409    private void HandleFriendBlockRequest(string userId, bool blockUser)
 410    {
 0411        var friendEntryToBlock = Get(userId);
 0412        if (friendEntryToBlock == null) return;
 413        // instantly refresh ui
 0414        friendEntryToBlock.Model.blocked = blockUser;
 0415        Set(userId, friendEntryToBlock.Model);
 0416    }
 417
 418    private void HandleUnfriendRequest(string userId)
 419    {
 0420        var entry = Get(userId);
 0421        if (entry == null) return;
 0422        Remove(userId);
 0423    }
 424
 2425    private void UpdateLayout() => isLayoutDirty = true;
 426
 427    private void SortDirtyLists()
 428    {
 126429        if (offlineFriendsList.IsSortingDirty)
 0430            offlineFriendsList.Sort();
 126431        if (onlineFriendsList.IsSortingDirty)
 0432            onlineFriendsList.Sort();
 433
 126434        if (searchResultsFriendList.IsSortingDirty)
 0435            searchResultsFriendList.Sort();
 126436    }
 437
 438    private void RequestMoreEntries(Vector2 position)
 439    {
 1440        if (!loadMoreEntriesContainer.activeInHierarchy ||
 441            loadMoreEntriesSpinner.activeInHierarchy ||
 1442            Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 443
 0444        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD &&
 445            lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD)
 446        {
 0447            if (requireMoreEntriesRoutine != null)
 0448                StopCoroutine(requireMoreEntriesRoutine);
 449
 0450            ShowMoreFriendsLoadingSpinner();
 0451            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 452
 0453            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 454        }
 455
 0456        lastScrollPosition = position;
 0457    }
 458
 459    private void ClearSearchResults()
 460    {
 2461        foreach (var pooledObj in searchPooledEntries.Values)
 0462            entryPool.Release(pooledObj);
 1463        searchPooledEntries.Clear();
 1464        searchResultsFriendList.list.Clear();
 1465    }
 466
 467    private void ClearOnlineAndOfflineFriends()
 468    {
 2469        foreach (var pooledObj in pooledEntries.Values)
 0470            entryPool.Release(pooledObj);
 471
 1472        pooledEntries.Clear();
 1473        onlineFriendsList.list.Clear();
 1474        offlineFriendsList.list.Clear();
 1475    }
 476
 477    private IEnumerator WaitThenRequireMoreEntries()
 478    {
 0479        yield return new WaitForSeconds(1f);
 0480        OnRequireMoreEntries?.Invoke();
 0481    }
 482
 483    [Serializable]
 484    private class FriendListComponents
 485    {
 486        public CollapsableSortedFriendEntryList list;
 487        public TMP_Text countText;
 488        public GameObject headerContainer;
 489        public int currentAvatarSnapshotIndex;
 490
 378491        public bool IsSortingDirty { get; private set; }
 492
 493        public void Show()
 494        {
 2495            list.Show();
 2496            headerContainer.SetActive(true);
 2497        }
 498
 499        public void Hide()
 500        {
 0501            list.Hide();
 0502            headerContainer.SetActive(false);
 0503        }
 504
 0505        public void FlagAsPendingToSort() => IsSortingDirty = true;
 506
 507        public void Sort()
 508        {
 0509            list.Sort();
 0510            IsSortingDirty = false;
 0511        }
 512    }
 513
 514    [Serializable]
 515    private class Model
 516    {
 517        public bool isOnlineFriendsExpanded;
 518        public bool isOfflineFriendsExpanded;
 519    }
 520}