< 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:181
Uncovered lines:59
Coverable lines:240
Total lines:524
Line coverage:75.4% (181 of 240)
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.633058.82%
Set(...)0%13.127050%
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%110100%
SortDirtyLists()0%4.374071.43%
RequestMoreEntries(...)0%7.057090%
ClearSearchResults()0%2.092071.43%
ClearOnlineAndOfflineFriends()0%2.062075%
WaitThenRequireMoreEntries()0%440100%
Show()0%110100%
Hide()0%2100%
FlagAsPendingToSort()0%110100%
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 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
 2444    private readonly Dictionary<string, FriendEntryModel> creationQueue =
 45        new Dictionary<string, FriendEntryModel>();
 46
 2447    private readonly Dictionary<string, PoolableObject> pooledEntries = new Dictionary<string, PoolableObject>();
 2448    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;
 2455    private Vector2 lastScrollPosition = Vector2.one;
 56    private Coroutine requireMoreEntriesRoutine;
 57    private float loadMoreEntriesRestrictionTime;
 58
 1759    public int Count => onlineFriendsList.list.Count()
 60                        + offlineFriendsList.list.Count()
 61                        + creationQueue.Keys.Count(s =>
 262                            !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<string> OnDeleteConfirmation;
 68    public event Action OnRequireMoreEntries;
 69
 70    public void Initialize(IChatController chatController,
 71        IFriendsController friendsController,
 72        ISocialAnalytics socialAnalytics)
 73    {
 2374        this.chatController = chatController;
 2375        this.friendsController = friendsController;
 2376        this.socialAnalytics = socialAnalytics;
 2377    }
 78
 79    public override void OnEnable()
 80    {
 2481        base.OnEnable();
 82
 2483        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search friend"});
 2484        searchBar.OnSearchText += HandleSearchInputChanged;
 2485        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 2486        contextMenuPanel.OnUnfriend += HandleUnfriendRequest;
 2487        scroll.onValueChanged.AddListener(RequestMoreEntries);
 88
 89        int SortByAlphabeticalOrder(FriendEntryBase u1, FriendEntryBase u2)
 90        {
 091            return string.Compare(u1.Model.userName, u2.Model.userName, StringComparison.InvariantCultureIgnoreCase);
 92        }
 93
 2494        onlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 2495        offlineFriendsList.list.SortingMethod = SortByAlphabeticalOrder;
 2496        searchResultsFriendList.list.SortingMethod = SortByAlphabeticalOrder;
 2497        UpdateLayout();
 2498        UpdateEmptyOrFilledState();
 99
 100        //TODO temporary, remove this and allFriendsList gameobjects later
 24101        allFriendsList.list.gameObject.SetActive(false);
 24102        allFriendsList.headerContainer.gameObject.SetActive(false);
 24103    }
 104
 105    public override void OnDisable()
 106    {
 24107        base.OnDisable();
 108
 24109        searchBar.OnSearchText -= HandleSearchInputChanged;
 24110        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 24111        contextMenuPanel.OnUnfriend -= HandleUnfriendRequest;
 24112        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 24113    }
 114
 115    public void Show()
 116    {
 8117        gameObject.SetActive(true);
 8118        enabledHeader.SetActive(true);
 8119        disabledHeader.SetActive(false);
 8120        searchBar.ClearSearch();
 8121    }
 122
 123    public void Hide()
 124    {
 7125        gameObject.SetActive(false);
 7126        enabledHeader.SetActive(false);
 7127        disabledHeader.SetActive(true);
 7128        contextMenuPanel.Hide();
 7129    }
 130
 131    public override void Update()
 132    {
 3678133        base.Update();
 134
 3678135        if (isLayoutDirty)
 22136            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 3678137        isLayoutDirty = false;
 138
 3678139        SortDirtyLists();
 3678140        FetchProfilePicturesForVisibleEntries();
 3678141        SetQueuedEntries();
 3678142    }
 143
 144    public void Clear()
 145    {
 1146        HideMoreFriendsLoadingSpinner();
 1147        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1148        scroll.verticalNormalizedPosition = 1f;
 1149        creationQueue.Clear();
 150
 1151        searchResultsFriendList.list.Clear();
 152
 1153        ClearSearchResults();
 1154        ClearOnlineAndOfflineFriends();
 155
 1156        UpdateEmptyOrFilledState();
 1157        UpdateCounterLabel();
 1158    }
 159
 160    public void Remove(string userId)
 161    {
 11162        if (creationQueue.ContainsKey(userId))
 1163            creationQueue.Remove(userId);
 164
 11165        if (pooledEntries.TryGetValue(userId, out var pooledObject))
 166        {
 2167            entryPool.Release(pooledObject);
 2168            pooledEntries.Remove(userId);
 169        }
 170
 11171        if (searchPooledEntries.TryGetValue(userId, out var searchPooledObject))
 172        {
 0173            entryPool.Release(searchPooledObject);
 0174            searchPooledEntries.Remove(userId);
 175        }
 176
 11177        offlineFriendsList.list.Remove(userId);
 11178        onlineFriendsList.list.Remove(userId);
 11179        searchResultsFriendList.list.Remove(userId);
 180
 11181        UpdateEmptyOrFilledState();
 11182        UpdateCounterLabel();
 11183        UpdateLayout();
 11184    }
 185
 186    public FriendEntry Get(string userId)
 187    {
 42188        return (onlineFriendsList.list.Get(userId)
 189                ?? offlineFriendsList.list.Get(userId)
 190                ?? searchResultsFriendList.list.Get(userId)) as FriendEntry;
 191    }
 192
 193    private void Populate(string userId, FriendEntryModel model, FriendEntryBase entry)
 194    {
 6195        entry.Populate(model);
 196
 6197        if (isSearchMode)
 198        {
 0199            searchResultsFriendList.list.Remove(userId);
 0200            searchResultsFriendList.list.Add(userId, entry);
 0201            searchResultsFriendList.FlagAsPendingToSort();
 0202            searchResultsFriendList.list.Filter(searchBar.Text);
 203        }
 204        else
 205        {
 6206            if (model.status == PresenceStatus.ONLINE)
 207            {
 0208                offlineFriendsList.list.Remove(userId);
 0209                onlineFriendsList.list.Add(userId, entry);
 0210                onlineFriendsList.FlagAsPendingToSort();
 211            }
 212            else
 213            {
 6214                onlineFriendsList.list.Remove(userId);
 6215                offlineFriendsList.list.Add(userId, entry);
 6216                offlineFriendsList.FlagAsPendingToSort();
 217            }
 218        }
 219
 6220        UpdateLayout();
 6221        UpdateEmptyOrFilledState();
 6222        UpdateCounterLabel();
 6223    }
 224
 225    private void Set(string userId, FriendEntryModel model)
 226    {
 6227        if (creationQueue.ContainsKey(userId))
 228        {
 0229            creationQueue[userId] = model;
 0230            return;
 231        }
 232
 233        FriendEntryBase entry;
 234
 6235        if (isSearchMode)
 236        {
 0237            if (!searchResultsFriendList.list.Contains(userId))
 0238                entry = CreateEntry(userId, searchPooledEntries);
 239            else
 0240                entry = searchResultsFriendList.list.Get(userId);
 241        }
 242        else
 243        {
 6244            if (!onlineFriendsList.list.Contains(userId)
 245                && !offlineFriendsList.list.Contains(userId))
 6246                entry = CreateEntry(userId, pooledEntries);
 247            else
 0248                entry = onlineFriendsList.list.Get(userId) ?? offlineFriendsList.list.Get(userId);
 249        }
 250
 6251        Populate(userId, model, entry);
 6252    }
 253
 254    public override void RefreshControl()
 255    {
 1256        onlineFriendsList.Show();
 1257        offlineFriendsList.Show();
 258
 1259        if (model.isOnlineFriendsExpanded)
 1260            onlineFriendsList.list.Expand();
 261        else
 0262            onlineFriendsList.list.Collapse();
 263
 1264        if (model.isOfflineFriendsExpanded)
 1265            offlineFriendsList.list.Expand();
 266        else
 0267            offlineFriendsList.list.Collapse();
 0268    }
 269
 270    public void DisableSearchMode()
 271    {
 0272        isSearchMode = false;
 273
 0274        ClearSearchResults();
 275
 0276        searchBar.ClearSearch(false);
 0277        searchResultsFriendList.list.Clear();
 0278        searchResultsFriendList.Hide();
 0279        offlineFriendsList.Show();
 0280        onlineFriendsList.Show();
 0281    }
 282
 283    public void EnableSearchMode()
 284    {
 0285        isSearchMode = true;
 286
 0287        ClearSearchResults();
 288
 0289        offlineFriendsList.Hide();
 0290        onlineFriendsList.Hide();
 0291        searchResultsFriendList.Show();
 292
 0293        UpdateCounterLabel();
 0294        HideMoreFriendsToLoadHint();
 0295        UpdateLayout();
 0296    }
 297
 7298    public void Enqueue(string userId, FriendEntryModel model) => creationQueue[userId] = model;
 299
 300    public void HideMoreFriendsToLoadHint()
 301    {
 1302        loadMoreEntriesContainer.SetActive(false);
 1303        UpdateLayout();
 1304    }
 305
 306    public void ShowMoreFriendsToLoadHint(int hiddenCount)
 307    {
 2308        loadMoreEntriesLabel.text =
 309            $"{hiddenCount} friends hidden. Use the search bar to find them or scroll down to show more.";
 2310        loadMoreEntriesContainer.SetActive(true);
 2311        UpdateLayout();
 2312    }
 313
 1314    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 315
 7316    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 317
 8318    private void HandleSearchInputChanged(string search) => OnSearchRequested?.Invoke(search);
 319
 320    private void SetQueuedEntries()
 321    {
 7350322        if (creationQueue.Count == 0) return;
 323
 24324        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 325        {
 6326            var pair = creationQueue.FirstOrDefault();
 6327            creationQueue.Remove(pair.Key);
 6328            Set(pair.Key, pair.Value);
 329        }
 330
 6331        HideMoreFriendsLoadingSpinner();
 6332    }
 333
 334    private void FetchProfilePicturesForVisibleEntries()
 335    {
 3678336        FetchProfilePicturesForVisibleEntries(onlineFriendsList);
 3678337        FetchProfilePicturesForVisibleEntries(offlineFriendsList);
 3678338        FetchProfilePicturesForVisibleEntries(searchResultsFriendList);
 3678339    }
 340
 341    private void FetchProfilePicturesForVisibleEntries(FriendListComponents friendListComponents)
 342    {
 29346343        foreach (var entry in friendListComponents.list.Entries.Values
 344                     .Skip(friendListComponents.currentAvatarSnapshotIndex)
 345                     .Take(AVATAR_SNAPSHOTS_PER_FRAME))
 346        {
 3639347            if (entry.IsVisible(viewport))
 3639348                entry.EnableAvatarSnapshotFetching();
 349            else
 0350                entry.DisableAvatarSnapshotFetching();
 351        }
 352
 11034353        friendListComponents.currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 354
 11034355        if (friendListComponents.currentAvatarSnapshotIndex >= friendListComponents.list.Count())
 11034356            friendListComponents.currentAvatarSnapshotIndex = 0;
 11034357    }
 358
 359    private void UpdateEmptyOrFilledState()
 360    {
 42361        var totalFriends = onlineFriendsList.list.Count() + offlineFriendsList.list.Count();
 42362        emptyStateContainer.SetActive(totalFriends == 0);
 42363        filledStateContainer.SetActive(totalFriends > 0);
 42364    }
 365
 366    private FriendEntry CreateEntry(string userId, Dictionary<string, PoolableObject> poolableObjects)
 367    {
 6368        entryPool = GetEntryPool();
 6369        var newFriendEntry = entryPool.Get();
 6370        poolableObjects.Add(userId, newFriendEntry);
 6371        var entry = newFriendEntry.gameObject.GetComponent<FriendEntry>();
 6372        entry.Initialize(chatController, friendsController, socialAnalytics);
 373
 6374        entry.OnMenuToggle -= OnEntryMenuToggle;
 6375        entry.OnMenuToggle += OnEntryMenuToggle;
 6376        entry.OnWhisperClick -= OnEntryWhisperClick;
 6377        entry.OnWhisperClick += OnEntryWhisperClick;
 378
 6379        return entry;
 380    }
 381
 0382    private void OnEntryWhisperClick(FriendEntry friendEntry) => OnWhisper?.Invoke(friendEntry.Model);
 383
 384    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 385    {
 0386        contextMenuPanel.Show(friendEntry.Model.userId);
 0387        friendEntry.Dock(contextMenuPanel);
 0388    }
 389
 390    private Pool GetEntryPool()
 391    {
 6392        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 6393        if (entryPool != null) return entryPool;
 394
 6395        entryPool = PoolManager.i.AddPool(
 396            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 397            Instantiate(entryPrefab).gameObject,
 398            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 399            isPersistent: true);
 6400        entryPool.ForcePrewarm();
 401
 6402        return entryPool;
 403    }
 404
 405    private void UpdateCounterLabel()
 406    {
 18407        onlineFriendsList.countText.SetText("ONLINE ({0})", onlineFriendsList.list.Count());
 18408        offlineFriendsList.countText.SetText("OFFLINE ({0})", offlineFriendsList.list.Count());
 18409        searchResultsFriendList.countText.SetText("Results ({0})", searchResultsFriendList.list.Count());
 18410    }
 411
 412    private void HandleFriendBlockRequest(string userId, bool blockUser)
 413    {
 0414        var friendEntryToBlock = Get(userId);
 0415        if (friendEntryToBlock == null) return;
 416        // instantly refresh ui
 0417        friendEntryToBlock.Model.blocked = blockUser;
 0418        Set(userId, friendEntryToBlock.Model);
 0419    }
 420
 421    private void HandleUnfriendRequest(string userId)
 422    {
 0423        var entry = Get(userId);
 0424        if (entry == null) return;
 0425        Remove(userId);
 0426        OnDeleteConfirmation?.Invoke(userId);
 0427    }
 428
 44429    private void UpdateLayout() => isLayoutDirty = true;
 430
 431    private void SortDirtyLists()
 432    {
 3678433        if (offlineFriendsList.IsSortingDirty)
 3434            offlineFriendsList.Sort();
 3678435        if (onlineFriendsList.IsSortingDirty)
 0436            onlineFriendsList.Sort();
 437
 3678438        if (searchResultsFriendList.IsSortingDirty)
 0439            searchResultsFriendList.Sort();
 3678440    }
 441
 442    private void RequestMoreEntries(Vector2 position)
 443    {
 2444        if (!loadMoreEntriesContainer.activeInHierarchy ||
 445            loadMoreEntriesSpinner.activeInHierarchy ||
 1446            Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 447
 1448        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD &&
 449            lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD)
 450        {
 1451            if (requireMoreEntriesRoutine != null)
 0452                StopCoroutine(requireMoreEntriesRoutine);
 453
 1454            ShowMoreFriendsLoadingSpinner();
 1455            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 456
 1457            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 458        }
 459
 1460        lastScrollPosition = position;
 1461    }
 462
 463    private void ClearSearchResults()
 464    {
 2465        foreach (var pooledObj in searchPooledEntries.Values)
 0466            entryPool.Release(pooledObj);
 1467        searchPooledEntries.Clear();
 1468        searchResultsFriendList.list.Clear();
 1469    }
 470
 471    private void ClearOnlineAndOfflineFriends()
 472    {
 2473        foreach (var pooledObj in pooledEntries.Values)
 0474            entryPool.Release(pooledObj);
 475
 1476        pooledEntries.Clear();
 1477        onlineFriendsList.list.Clear();
 1478        offlineFriendsList.list.Clear();
 1479    }
 480
 481    private IEnumerator WaitThenRequireMoreEntries()
 482    {
 1483        yield return new WaitForSeconds(1f);
 1484        OnRequireMoreEntries?.Invoke();
 1485    }
 486
 487    [Serializable]
 488    private class FriendListComponents
 489    {
 490        public CollapsableSortedFriendEntryList list;
 491        public TMP_Text countText;
 492        public GameObject headerContainer;
 493        public int currentAvatarSnapshotIndex;
 494
 11043495        public bool IsSortingDirty { get; private set; }
 496
 497        public void Show()
 498        {
 2499            list.Show();
 2500            headerContainer.SetActive(true);
 2501        }
 502
 503        public void Hide()
 504        {
 0505            list.Hide();
 0506            headerContainer.SetActive(false);
 0507        }
 508
 6509        public void FlagAsPendingToSort() => IsSortingDirty = true;
 510
 511        public void Sort()
 512        {
 3513            list.Sort();
 3514            IsSortingDirty = false;
 3515        }
 516    }
 517
 518    [Serializable]
 519    private class Model
 520    {
 521        public bool isOnlineFriendsExpanded;
 522        public bool isOfflineFriendsExpanded;
 523    }
 524}