< 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:177
Uncovered lines:66
Coverable lines:243
Total lines:522
Line coverage:72.8% (177 of 243)
Covered branches:0
Total branches:0

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%110100%
Hide()0%110100%
Update()0%220100%
Clear()0%110100%
Remove(...)0%4.044086.67%
Get(...)0%330100%
Populate(...)0%3.963052.63%
Set(...)0%14.657046.15%
RefreshControl()0%3.333066.67%
DisableSearchMode()0%2100%
EnableSearchMode()0%2100%
Enqueue(...)0%110100%
HideMoreFriendsToLoadHint()0%110100%
ShowMoreFriendsToLoadHint(...)0%110100%
ShowMoreFriendsLoadingSpinner()0%110100%
HideMoreFriendsLoadingSpinner()0%110100%
HandleSearchInputChanged(...)0%220100%
SetQueuedEntries()0%440100%
FetchProfilePicturesForVisibleEntries()0%110100%
FetchProfilePicturesForVisibleEntries(...)0%5.025090%
UpdateEmptyOrFilledState()0%110100%
CreateEntry(...)0%110100%
OnEntryWhisperClick(...)0%6200%
OnEntryMenuToggle(...)0%2100%
GetEntryPool()0%2.022083.33%
UpdateCounterLabel()0%110100%
HandleFriendBlockRequest(...)0%6200%
HandleUnfriendRequest(...)0%12300%
UpdateLayout()0%2100%
SortDirtyLists()0%4.374071.43%
RequestMoreEntries(...)0%7.397080%
ClearSearchResults()0%2.092071.43%
ClearOnlineAndOfflineFriends()0%2.062075%
WaitThenRequireMoreEntries()0%440100%
Show()0%110100%
Hide()0%2100%
FlagAsPendingToSort()0%2100%
Sort()0%110100%

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 SocialFeaturesAnalytics;
 8using TMPro;
 9using UnityEngine;
 10using UnityEngine.UI;
 11
 12public class FriendsTabComponentView : BaseComponentView
 13{
 14    private const int CREATION_AMOUNT_PER_FRAME = 5;
 15    private const int AVATAR_SNAPSHOTS_PER_FRAME = 5;
 16    private const int PRE_INSTANTIATED_ENTRIES = 25;
 17    private const string FRIEND_ENTRIES_POOL_NAME_PREFIX = "FriendEntriesPool_";
 18    private const float REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD = 0.005f;
 19    private const float MIN_TIME_TO_REQUIRE_MORE_ENTRIES = 2f;
 20
 21    [SerializeField] private GameObject enabledHeader;
 22    [SerializeField] private GameObject disabledHeader;
 23    [SerializeField] private GameObject emptyStateContainer;
 24    [SerializeField] private GameObject filledStateContainer;
 25    [SerializeField] private FriendEntry entryPrefab;
 26    [SerializeField] private FriendListComponents onlineFriendsList;
 27    [SerializeField] private FriendListComponents offlineFriendsList;
 28    [SerializeField] private FriendListComponents allFriendsList;
 29    [SerializeField] private FriendListComponents searchResultsFriendList;
 30    [SerializeField] private SearchBarComponentView searchBar;
 31    [SerializeField] private UserContextMenu contextMenuPanel;
 32    [SerializeField] private Model model;
 33    [SerializeField] private RectTransform viewport;
 34    [SerializeField] internal ScrollRect scroll;
 35
 36    [Header("Load More Entries")] [SerializeField]
 37    internal GameObject loadMoreEntriesContainer;
 38
 39    [SerializeField] internal TMP_Text loadMoreEntriesLabel;
 40    [SerializeField] internal GameObject loadMoreEntriesSpinner;
 41
 2442    private readonly Dictionary<string, FriendEntryModel> creationQueue =
 43        new Dictionary<string, FriendEntryModel>();
 44
 2445    private readonly Dictionary<string, PoolableObject> pooledEntries = new Dictionary<string, PoolableObject>();
 2446    private readonly Dictionary<string, PoolableObject> searchPooledEntries = new Dictionary<string, PoolableObject>();
 47    private Pool entryPool;
 48    private bool isLayoutDirty;
 49    private IChatController chatController;
 50    private IFriendsController friendsController;
 51    private ISocialAnalytics socialAnalytics;
 52    private bool isSearchMode;
 2453    private Vector2 lastScrollPosition = Vector2.one;
 54    private Coroutine requireMoreEntriesRoutine;
 55    private float loadMoreEntriesRestrictionTime;
 56
 1757    public int Count => onlineFriendsList.list.Count()
 58                        + offlineFriendsList.list.Count()
 59                        + creationQueue.Keys.Count(s =>
 260                            !onlineFriendsList.list.Contains(s) && !offlineFriendsList.list.Contains(s));
 61
 62    public event Action<string> OnSearchRequested;
 63
 64    public event Action<FriendEntryModel> OnWhisper;
 65    public event Action<string> OnDeleteConfirmation;
 66    public event Action OnRequireMoreEntries;
 67
 68    public void Initialize(IChatController chatController,
 69        IFriendsController friendsController,
 70        ISocialAnalytics socialAnalytics)
 71    {
 2372        this.chatController = chatController;
 2373        this.friendsController = friendsController;
 2374        this.socialAnalytics = socialAnalytics;
 2375    }
 76
 77    public override void OnEnable()
 78    {
 2479        base.OnEnable();
 80
 2481        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search friend"});
 2482        searchBar.OnSearchText += HandleSearchInputChanged;
 2483        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 2484        contextMenuPanel.OnUnfriend += HandleUnfriendRequest;
 2485        scroll.onValueChanged.AddListener(RequestMoreEntries);
 86
 87        int SortByAlphabeticalOrder(FriendEntryBase u1, FriendEntryBase u2)
 88        {
 089            return string.Compare(u1.Model.userName, u2.Model.userName, StringComparison.InvariantCultureIgnoreCase);
 90        }
 91
 2492        onlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 2493        offlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 2494        searchResultsFriendList.list.SortingMethod = SortByAlphabeticalOrder;
 2495        UpdateLayout();
 2496        UpdateEmptyOrFilledState();
 97
 98        //TODO temporary, remove this and allFriendsList gameobjects later
 2499        allFriendsList.list.gameObject.SetActive(false);
 24100        allFriendsList.headerContainer.gameObject.SetActive(false);
 24101    }
 102
 103    public override void OnDisable()
 104    {
 24105        base.OnDisable();
 106
 24107        searchBar.OnSearchText -= HandleSearchInputChanged;
 24108        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 24109        contextMenuPanel.OnUnfriend -= HandleUnfriendRequest;
 24110        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 24111    }
 112
 113    public void Show()
 114    {
 8115        gameObject.SetActive(true);
 8116        enabledHeader.SetActive(true);
 8117        disabledHeader.SetActive(false);
 8118        searchBar.ClearSearch();
 8119    }
 120
 121    public void Hide()
 122    {
 7123        gameObject.SetActive(false);
 7124        enabledHeader.SetActive(false);
 7125        disabledHeader.SetActive(true);
 7126        contextMenuPanel.Hide();
 7127    }
 128
 129    public override void Update()
 130    {
 354131        base.Update();
 132
 354133        if (isLayoutDirty)
 22134            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 354135        isLayoutDirty = false;
 136
 354137        SortDirtyLists();
 354138        FetchProfilePicturesForVisibleEntries();
 354139        SetQueuedEntries();
 354140    }
 141
 142    public void Clear()
 143    {
 1144        HideMoreFriendsLoadingSpinner();
 1145        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1146        scroll.verticalNormalizedPosition = 1f;
 1147        creationQueue.Clear();
 148
 1149        searchResultsFriendList.list.Clear();
 150
 1151        ClearSearchResults();
 1152        ClearOnlineAndOfflineFriends();
 153
 1154        UpdateEmptyOrFilledState();
 1155        UpdateCounterLabel();
 1156    }
 157
 158    public void Remove(string userId)
 159    {
 11160        if (creationQueue.ContainsKey(userId))
 1161            creationQueue.Remove(userId);
 162
 11163        if (pooledEntries.TryGetValue(userId, out var pooledObject))
 164        {
 2165            entryPool.Release(pooledObject);
 2166            pooledEntries.Remove(userId);
 167        }
 168
 11169        if (searchPooledEntries.TryGetValue(userId, out var searchPooledObject))
 170        {
 0171            entryPool.Release(searchPooledObject);
 0172            searchPooledEntries.Remove(userId);
 173        }
 174
 11175        offlineFriendsList.list.Remove(userId);
 11176        onlineFriendsList.list.Remove(userId);
 11177        searchResultsFriendList.list.Remove(userId);
 178
 11179        UpdateEmptyOrFilledState();
 11180        UpdateCounterLabel();
 11181        UpdateLayout();
 11182    }
 183
 184    public FriendEntry Get(string userId)
 185    {
 42186        return (onlineFriendsList.list.Get(userId)
 187                ?? offlineFriendsList.list.Get(userId)
 188                ?? searchResultsFriendList.list.Get(userId)) as FriendEntry;
 189    }
 190
 191    private void Populate(string userId, FriendEntryModel model, FriendEntryBase entry)
 192    {
 6193        entry.Populate(model);
 194
 6195        if (isSearchMode)
 196        {
 0197            searchResultsFriendList.list.Remove(userId);
 0198            searchResultsFriendList.list.Add(userId, entry);
 0199            searchResultsFriendList.FlagAsPendingToSort();
 0200            searchResultsFriendList.list.Filter(searchBar.Text);
 0201        }
 202        else
 203        {
 6204            if (model.status == PresenceStatus.ONLINE)
 205            {
 0206                offlineFriendsList.list.Remove(userId);
 0207                onlineFriendsList.list.Add(userId, entry);
 0208                onlineFriendsList.FlagAsPendingToSort();
 0209            }
 210            else
 211            {
 6212                onlineFriendsList.list.Remove(userId);
 6213                offlineFriendsList.list.Add(userId, entry);
 6214                offlineFriendsList.FlagAsPendingToSort();
 215            }
 216        }
 217
 6218        UpdateLayout();
 6219        UpdateEmptyOrFilledState();
 6220        UpdateCounterLabel();
 6221    }
 222
 223    private void Set(string userId, FriendEntryModel model)
 224    {
 6225        if (creationQueue.ContainsKey(userId))
 226        {
 0227            creationQueue[userId] = model;
 0228            return;
 229        }
 230
 231        FriendEntryBase entry;
 232
 6233        if (isSearchMode)
 234        {
 0235            if (!searchResultsFriendList.list.Contains(userId))
 0236                entry = CreateEntry(userId, searchPooledEntries);
 237            else
 0238                entry = searchResultsFriendList.list.Get(userId);
 0239        }
 240        else
 241        {
 6242            if (!onlineFriendsList.list.Contains(userId)
 243                && !offlineFriendsList.list.Contains(userId))
 6244                entry = CreateEntry(userId, pooledEntries);
 245            else
 0246                entry = onlineFriendsList.list.Get(userId) ?? offlineFriendsList.list.Get(userId);
 247        }
 248
 6249        Populate(userId, model, entry);
 6250    }
 251
 252    public override void RefreshControl()
 253    {
 1254        onlineFriendsList.Show();
 1255        offlineFriendsList.Show();
 256
 1257        if (model.isOnlineFriendsExpanded)
 1258            onlineFriendsList.list.Expand();
 259        else
 0260            onlineFriendsList.list.Collapse();
 261
 1262        if (model.isOfflineFriendsExpanded)
 1263            offlineFriendsList.list.Expand();
 264        else
 0265            offlineFriendsList.list.Collapse();
 0266    }
 267
 268    public void DisableSearchMode()
 269    {
 0270        isSearchMode = false;
 271
 0272        ClearSearchResults();
 273
 0274        searchBar.ClearSearch(false);
 0275        searchResultsFriendList.list.Clear();
 0276        searchResultsFriendList.Hide();
 0277        offlineFriendsList.Show();
 0278        onlineFriendsList.Show();
 0279    }
 280
 281    public void EnableSearchMode()
 282    {
 0283        isSearchMode = true;
 284
 0285        ClearSearchResults();
 286
 0287        offlineFriendsList.Hide();
 0288        onlineFriendsList.Hide();
 0289        searchResultsFriendList.Show();
 290
 0291        UpdateCounterLabel();
 0292        HideMoreFriendsToLoadHint();
 0293        UpdateLayout();
 0294    }
 295
 7296    public void Enqueue(string userId, FriendEntryModel model) => creationQueue[userId] = model;
 297
 298    public void HideMoreFriendsToLoadHint()
 299    {
 1300        loadMoreEntriesContainer.SetActive(false);
 1301        UpdateLayout();
 1302    }
 303
 304    public void ShowMoreFriendsToLoadHint(int hiddenCount)
 305    {
 2306        loadMoreEntriesLabel.text =
 307            $"{hiddenCount} friends hidden. Use the search bar to find them or scroll down to show more.";
 2308        loadMoreEntriesContainer.SetActive(true);
 2309        UpdateLayout();
 2310    }
 311
 1312    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 313
 7314    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 315
 8316    private void HandleSearchInputChanged(string search) => OnSearchRequested?.Invoke(search);
 317
 318    private void SetQueuedEntries()
 319    {
 702320        if (creationQueue.Count == 0) return;
 321
 24322        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 323        {
 6324            var pair = creationQueue.FirstOrDefault();
 6325            creationQueue.Remove(pair.Key);
 6326            Set(pair.Key, pair.Value);
 327        }
 328
 6329        HideMoreFriendsLoadingSpinner();
 6330    }
 331
 332    private void FetchProfilePicturesForVisibleEntries()
 333    {
 354334        FetchProfilePicturesForVisibleEntries(onlineFriendsList);
 354335        FetchProfilePicturesForVisibleEntries(offlineFriendsList);
 354336        FetchProfilePicturesForVisibleEntries(searchResultsFriendList);
 354337    }
 338
 339    private void FetchProfilePicturesForVisibleEntries(FriendListComponents friendListComponents)
 340    {
 2754341        foreach (var entry in friendListComponents.list.Entries.Values
 342                     .Skip(friendListComponents.currentAvatarSnapshotIndex)
 343                     .Take(AVATAR_SNAPSHOTS_PER_FRAME))
 344        {
 315345            if (entry.IsVisible(viewport))
 315346                entry.EnableAvatarSnapshotFetching();
 347            else
 0348                entry.DisableAvatarSnapshotFetching();
 349        }
 350
 1062351        friendListComponents.currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 352
 1062353        if (friendListComponents.currentAvatarSnapshotIndex >= friendListComponents.list.Count())
 1062354            friendListComponents.currentAvatarSnapshotIndex = 0;
 1062355    }
 356
 357    private void UpdateEmptyOrFilledState()
 358    {
 42359        var totalFriends = onlineFriendsList.list.Count() + offlineFriendsList.list.Count();
 42360        emptyStateContainer.SetActive(totalFriends == 0);
 42361        filledStateContainer.SetActive(totalFriends > 0);
 42362    }
 363
 364    private FriendEntry CreateEntry(string userId, Dictionary<string, PoolableObject> poolableObjects)
 365    {
 6366        entryPool = GetEntryPool();
 6367        var newFriendEntry = entryPool.Get();
 6368        poolableObjects.Add(userId, newFriendEntry);
 6369        var entry = newFriendEntry.gameObject.GetComponent<FriendEntry>();
 6370        entry.Initialize(chatController, friendsController, socialAnalytics);
 371
 6372        entry.OnMenuToggle -= OnEntryMenuToggle;
 6373        entry.OnMenuToggle += OnEntryMenuToggle;
 6374        entry.OnWhisperClick -= OnEntryWhisperClick;
 6375        entry.OnWhisperClick += OnEntryWhisperClick;
 376
 6377        return entry;
 378    }
 379
 0380    private void OnEntryWhisperClick(FriendEntry friendEntry) => OnWhisper?.Invoke(friendEntry.Model);
 381
 382    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 383    {
 0384        contextMenuPanel.Show(friendEntry.Model.userId);
 0385        friendEntry.Dock(contextMenuPanel);
 0386    }
 387
 388    private Pool GetEntryPool()
 389    {
 6390        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 6391        if (entryPool != null) return entryPool;
 392
 6393        entryPool = PoolManager.i.AddPool(
 394            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 395            Instantiate(entryPrefab).gameObject,
 396            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 397            isPersistent: true);
 6398        entryPool.ForcePrewarm();
 399
 6400        return entryPool;
 401    }
 402
 403    private void UpdateCounterLabel()
 404    {
 18405        onlineFriendsList.countText.SetText("ONLINE ({0})", onlineFriendsList.list.Count());
 18406        offlineFriendsList.countText.SetText("OFFLINE ({0})", offlineFriendsList.list.Count());
 18407        searchResultsFriendList.countText.SetText("Results ({0})", searchResultsFriendList.list.Count());
 18408    }
 409
 410    private void HandleFriendBlockRequest(string userId, bool blockUser)
 411    {
 0412        var friendEntryToBlock = Get(userId);
 0413        if (friendEntryToBlock == null) return;
 414        // instantly refresh ui
 0415        friendEntryToBlock.Model.blocked = blockUser;
 0416        Set(userId, friendEntryToBlock.Model);
 0417    }
 418
 419    private void HandleUnfriendRequest(string userId)
 420    {
 0421        var entry = Get(userId);
 0422        if (entry == null) return;
 0423        Remove(userId);
 0424        OnDeleteConfirmation?.Invoke(userId);
 0425    }
 426
 0427    private void UpdateLayout() => isLayoutDirty = true;
 428
 429    private void SortDirtyLists()
 430    {
 354431        if (offlineFriendsList.IsSortingDirty)
 3432            offlineFriendsList.Sort();
 354433        if (onlineFriendsList.IsSortingDirty)
 0434            onlineFriendsList.Sort();
 435
 354436        if (searchResultsFriendList.IsSortingDirty)
 0437            searchResultsFriendList.Sort();
 354438    }
 439
 440    private void RequestMoreEntries(Vector2 position)
 441    {
 1442        if (!loadMoreEntriesContainer.activeInHierarchy ||
 443            loadMoreEntriesSpinner.activeInHierarchy ||
 0444            Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 445
 1446        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD &&
 447            lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD)
 448        {
 1449            if (requireMoreEntriesRoutine != null)
 0450                StopCoroutine(requireMoreEntriesRoutine);
 451
 1452            ShowMoreFriendsLoadingSpinner();
 1453            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 454
 1455            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 456        }
 457
 1458        lastScrollPosition = position;
 1459    }
 460
 461    private void ClearSearchResults()
 462    {
 2463        foreach (var pooledObj in searchPooledEntries.Values)
 0464            entryPool.Release(pooledObj);
 1465        searchPooledEntries.Clear();
 1466        searchResultsFriendList.list.Clear();
 1467    }
 468
 469    private void ClearOnlineAndOfflineFriends()
 470    {
 2471        foreach (var pooledObj in pooledEntries.Values)
 0472            entryPool.Release(pooledObj);
 473
 1474        pooledEntries.Clear();
 1475        onlineFriendsList.list.Clear();
 1476        offlineFriendsList.list.Clear();
 1477    }
 478
 479    private IEnumerator WaitThenRequireMoreEntries()
 480    {
 1481        yield return new WaitForSeconds(1f);
 1482        OnRequireMoreEntries?.Invoke();
 1483    }
 484
 485    [Serializable]
 486    private class FriendListComponents
 487    {
 488        public CollapsableSortedFriendEntryList list;
 489        public TMP_Text countText;
 490        public GameObject headerContainer;
 491        public int currentAvatarSnapshotIndex;
 492
 0493        public bool IsSortingDirty { get; private set; }
 494
 495        public void Show()
 496        {
 2497            list.Show();
 2498            headerContainer.SetActive(true);
 2499        }
 500
 501        public void Hide()
 502        {
 0503            list.Hide();
 0504            headerContainer.SetActive(false);
 0505        }
 506
 0507        public void FlagAsPendingToSort() => IsSortingDirty = true;
 508
 509        public void Sort()
 510        {
 3511            list.Sort();
 3512            IsSortingDirty = false;
 3513        }
 514    }
 515
 516    [Serializable]
 517    private class Model
 518    {
 519        public bool isOnlineFriendsExpanded;
 520        public bool isOfflineFriendsExpanded;
 521    }
 522}