< Summary

Class:FriendRequestsTabComponentView
Assembly:FriendsHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/FriendsHUD/Scripts/Tabs/FriendRequestsTabComponentView.cs
Covered lines:143
Uncovered lines:69
Coverable lines:212
Total lines:449
Line coverage:67.4% (143 of 212)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
FriendRequestsTabComponentView()0%110100%
OnEnable()0%110100%
OnDisable()0%220100%
Update()0%220100%
Expand()0%110100%
Show()0%110100%
Hide()0%110100%
RefreshControl()0%20400%
Remove(...)0%4.014092.86%
Clear()0%110100%
Get(...)0%220100%
Enqueue(...)0%110100%
Set(...)0%3.073080%
Populate(...)0%4.434070%
ShowUserNotFoundNotification()0%6200%
UpdateLayout()0%2100%
CreateEntry(...)0%22093.75%
GetEntryPool()0%2.022083.33%
SendFriendRequest(...)0%12300%
ShowAlreadyFriendsNotification()0%6200%
ShowRequestSuccessfullySentNotification()0%6200%
HideMoreFriendsToLoadHint()0%110100%
ShowMoreEntriesToLoadHint(...)0%110100%
ShowMoreFriendsLoadingSpinner()0%110100%
HideMoreFriendsLoadingSpinner()0%110100%
UpdateEmptyOrFilledState()0%110100%
OnSearchInputValueChanged(...)0%3.333066.67%
OnFriendRequestReceivedAccepted(...)0%6200%
ShowFriendAcceptedNotification(...)0%6200%
OnEntryRejectButtonPressed(...)0%6200%
OnEntryCancelButtonPressed(...)0%6200%
OnEntryMenuToggle(...)0%2100%
UpdateCounterLabel()0%110100%
HandleFriendBlockRequest(...)0%6200%
FetchProfilePicturesForVisibleEntries()0%5.025090%
SetQueuedEntries()0%440100%
RequestMoreEntries(...)0%7.397080%
WaitThenRequireMoreEntries()0%440100%
Model()0%110100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using DCL;
 6using DCL.Helpers;
 7using TMPro;
 8using UnityEngine;
 9using UnityEngine.UI;
 10
 11public class FriendRequestsTabComponentView : BaseComponentView
 12{
 13    private const string FRIEND_ENTRIES_POOL_NAME_PREFIX = "FriendRequestEntriesPool_";
 14    private const string NOTIFICATIONS_ID = "Friends";
 15    private const int PRE_INSTANTIATED_ENTRIES = 25;
 16    private const float NOTIFICATIONS_DURATION = 3;
 17    private const int AVATAR_SNAPSHOTS_PER_FRAME = 5;
 18    private const int CREATION_AMOUNT_PER_FRAME = 5;
 19    private const float REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD = 0.005f;
 20    private const float MIN_TIME_TO_REQUIRE_MORE_ENTRIES = 0.5f;
 21
 22    [SerializeField] private GameObject enabledHeader;
 23    [SerializeField] private GameObject disabledHeader;
 24    [SerializeField] private GameObject emptyStateContainer;
 25    [SerializeField] private GameObject filledStateContainer;
 26    [SerializeField] private FriendRequestEntry entryPrefab;
 27    [SerializeField] private CollapsableSortedFriendEntryList receivedRequestsList;
 28    [SerializeField] private CollapsableSortedFriendEntryList sentRequestsList;
 29    [SerializeField] private SearchBarComponentView searchBar;
 30    [SerializeField] private TMP_Text receivedRequestsCountText;
 31    [SerializeField] private TMP_Text sentRequestsCountText;
 32    [SerializeField] private UserContextMenu contextMenuPanel;
 33    [SerializeField] private RectTransform viewport;
 34    [SerializeField] internal ScrollRect scroll;
 35
 36    [Header("Notifications")] [SerializeField]
 37    private Notification requestSentNotification;
 38
 39    [SerializeField] private Notification friendSearchFailedNotification;
 40    [SerializeField] private Notification acceptedFriendNotification;
 41    [SerializeField] private Notification alreadyFriendsNotification;
 42    [SerializeField] private Model model;
 43
 44    [Header("Load More Entries")]
 45    [SerializeField] internal GameObject loadMoreEntriesContainer;
 46    [SerializeField] internal TMP_Text loadMoreEntriesLabel;
 47    [SerializeField] internal GameObject loadMoreEntriesSpinner;
 48
 2449    private readonly Dictionary<string, PoolableObject> pooleableEntries = new Dictionary<string, PoolableObject>();
 2450    private readonly Dictionary<string, FriendRequestEntry> entries = new Dictionary<string, FriendRequestEntry>();
 2451    private readonly Dictionary<string, FriendRequestEntryModel> creationQueue =
 52        new Dictionary<string, FriendRequestEntryModel>();
 53    private Pool entryPool;
 54    private string lastRequestSentUserName;
 55    private int currentAvatarSnapshotIndex;
 56    private bool isLayoutDirty;
 2457    private Vector2 lastScrollPosition = Vector2.one;
 58    private Coroutine requireMoreEntriesRoutine;
 59    private float loadMoreEntriesRestrictionTime;
 60
 061    public Dictionary<string, FriendRequestEntry> Entries => entries;
 62
 1463    public int Count => Entries.Count + creationQueue.Keys.Count(s => !Entries.ContainsKey(s));
 64
 065    public int ReceivedCount => receivedRequestsList.Count() +
 066                                creationQueue.Count(pair => pair.Value.isReceived && !Entries.ContainsKey(pair.Key));
 067    public int SentCount => sentRequestsList.Count() +
 068                                creationQueue.Count(pair => !pair.Value.isReceived && !Entries.ContainsKey(pair.Key));
 69
 70    public event Action<FriendRequestEntryModel> OnCancelConfirmation;
 71    public event Action<FriendRequestEntryModel> OnRejectConfirmation;
 72    public event Action<FriendRequestEntryModel> OnFriendRequestApproved;
 73    public event Action<string> OnFriendRequestSent;
 74    public event Action OnRequireMoreEntries;
 75
 76    public override void OnEnable()
 77    {
 778        base.OnEnable();
 779        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search a friend you want to add"});
 780        searchBar.OnSubmit += SendFriendRequest;
 781        searchBar.OnSearchText += OnSearchInputValueChanged;
 782        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 783        scroll.onValueChanged.AddListener(RequestMoreEntries);
 784        UpdateLayout();
 785    }
 86
 87    public override void OnDisable()
 88    {
 789        base.OnDisable();
 790        searchBar.OnSubmit -= SendFriendRequest;
 791        searchBar.OnSearchText -= OnSearchInputValueChanged;
 792        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 793        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 794        NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 795    }
 96
 97    public override void Update()
 98    {
 33199        base.Update();
 100
 331101        if (isLayoutDirty)
 8102            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 331103        isLayoutDirty = false;
 104
 331105        SetQueuedEntries();
 331106        FetchProfilePicturesForVisibleEntries();
 331107    }
 108
 109    public void Expand()
 110    {
 23111        receivedRequestsList.Expand();
 23112        sentRequestsList.Expand();
 23113    }
 114
 115    public void Show()
 116    {
 7117        gameObject.SetActive(true);
 7118        enabledHeader.SetActive(true);
 7119        disabledHeader.SetActive(false);
 7120        searchBar.ClearSearch();
 7121    }
 122
 123    public void Hide()
 124    {
 8125        gameObject.SetActive(false);
 8126        enabledHeader.SetActive(false);
 8127        disabledHeader.SetActive(true);
 8128        contextMenuPanel.Hide();
 8129    }
 130
 131    public override void RefreshControl()
 132    {
 0133        Clear();
 134
 0135        foreach (var friend in model.friends)
 0136            Set(friend.userId, friend.model);
 137
 0138        if (model.isReceivedRequestsExpanded)
 0139            receivedRequestsList.Expand();
 140        else
 0141            receivedRequestsList.Collapse();
 142
 0143        if (model.isSentRequestsExpanded)
 0144            sentRequestsList.Expand();
 145        else
 0146            sentRequestsList.Collapse();
 0147    }
 148
 149    public void Remove(string userId)
 150    {
 12151        if (creationQueue.ContainsKey(userId))
 0152            creationQueue.Remove(userId);
 153
 23154        if (!entries.ContainsKey(userId)) return;
 155
 1156        if (pooleableEntries.TryGetValue(userId, out var pooleableObject))
 157        {
 1158            entryPool.Release(pooleableObject);
 1159            pooleableEntries.Remove(userId);
 160        }
 161
 1162        entries.Remove(userId);
 1163        receivedRequestsList.Remove(userId);
 1164        sentRequestsList.Remove(userId);
 165
 1166        UpdateEmptyOrFilledState();
 1167        UpdateCounterLabel();
 1168        UpdateLayout();
 1169    }
 170
 171    public void Clear()
 172    {
 1173        HideMoreFriendsLoadingSpinner();
 1174        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1175        scroll.verticalNormalizedPosition = 1f;
 1176        creationQueue.Clear();
 1177        entries.ToList().ForEach(pair => Remove(pair.Key));
 1178        receivedRequestsList.Clear();
 1179        sentRequestsList.Clear();
 1180        UpdateEmptyOrFilledState();
 1181        UpdateCounterLabel();
 1182    }
 183
 37184    public FriendRequestEntry Get(string userId) => entries.ContainsKey(userId) ? entries[userId] : null;
 185
 6186    public void Enqueue(string userId, FriendRequestEntryModel model) => creationQueue[userId] = model;
 187
 188    public void Set(string userId, FriendRequestEntryModel model)
 189    {
 6190        if (creationQueue.ContainsKey(userId))
 191        {
 0192            creationQueue[userId] = model;
 0193            return;
 194        }
 195
 6196        if (!entries.ContainsKey(userId))
 197        {
 6198            CreateEntry(userId);
 6199            UpdateLayout();
 200        }
 201
 6202        Populate(userId, model);
 6203        UpdateEmptyOrFilledState();
 6204        UpdateCounterLabel();
 6205    }
 206
 207    public void Populate(string userId, FriendRequestEntryModel model)
 208    {
 6209        if (!entries.ContainsKey(userId))
 210        {
 0211            if (creationQueue.ContainsKey(userId))
 0212                creationQueue[userId] = model;
 0213            return;
 214        }
 215
 6216        var entry = entries[userId];
 6217        entry.Populate(model);
 218
 6219        if (model.isReceived)
 4220            receivedRequestsList.Add(userId, entry);
 221        else
 2222            sentRequestsList.Add(userId, entry);
 2223    }
 224
 225    public void ShowUserNotFoundNotification()
 226    {
 0227        friendSearchFailedNotification.model.timer = NOTIFICATIONS_DURATION;
 0228        friendSearchFailedNotification.model.groupID = NOTIFICATIONS_ID;
 0229        NotificationsController.i?.ShowNotification(friendSearchFailedNotification);
 0230    }
 231
 0232    private void UpdateLayout() => isLayoutDirty = true;
 233
 234    private void CreateEntry(string userId)
 235    {
 6236        entryPool = GetEntryPool();
 6237        var newFriendEntry = entryPool.Get();
 6238        pooleableEntries.Add(userId, newFriendEntry);
 6239        var entry = newFriendEntry.gameObject.GetComponent<FriendRequestEntry>();
 6240        if (entry == null) return;
 6241        entries.Add(userId, entry);
 242
 6243        entry.OnAccepted -= OnFriendRequestReceivedAccepted;
 6244        entry.OnAccepted += OnFriendRequestReceivedAccepted;
 6245        entry.OnRejected -= OnEntryRejectButtonPressed;
 6246        entry.OnRejected += OnEntryRejectButtonPressed;
 6247        entry.OnCancelled -= OnEntryCancelButtonPressed;
 6248        entry.OnCancelled += OnEntryCancelButtonPressed;
 6249        entry.OnMenuToggle -= OnEntryMenuToggle;
 6250        entry.OnMenuToggle += OnEntryMenuToggle;
 6251    }
 252
 253    private Pool GetEntryPool()
 254    {
 6255        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 6256        if (entryPool != null) return entryPool;
 257
 6258        entryPool = PoolManager.i.AddPool(
 259            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 260            Instantiate(entryPrefab).gameObject,
 261            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 262            isPersistent: true);
 6263        entryPool.ForcePrewarm();
 264
 6265        return entryPool;
 266    }
 267
 268    private void SendFriendRequest(string friendUserName)
 269    {
 0270        friendUserName = friendUserName.Trim()
 271            .Replace("\n", "")
 272            .Replace("\r", "");
 0273        if (string.IsNullOrEmpty(friendUserName)) return;
 274
 0275        searchBar.ClearSearch();
 0276        lastRequestSentUserName = friendUserName;
 0277        OnFriendRequestSent?.Invoke(friendUserName);
 0278    }
 279
 280    public void ShowAlreadyFriendsNotification()
 281    {
 0282        alreadyFriendsNotification.model.timer = NOTIFICATIONS_DURATION;
 0283        alreadyFriendsNotification.model.groupID = NOTIFICATIONS_ID;
 0284        NotificationsController.i?.ShowNotification(alreadyFriendsNotification);
 0285    }
 286
 287    public void ShowRequestSuccessfullySentNotification()
 288    {
 0289        requestSentNotification.model.timer = NOTIFICATIONS_DURATION;
 0290        requestSentNotification.model.groupID = NOTIFICATIONS_ID;
 0291        requestSentNotification.model.message = $"Your request to {lastRequestSentUserName} successfully sent!";
 0292        NotificationsController.i?.ShowNotification(requestSentNotification);
 0293    }
 294
 295    public void HideMoreFriendsToLoadHint()
 296    {
 1297        loadMoreEntriesContainer.SetActive(false);
 1298        UpdateLayout();
 1299    }
 300
 301    public void ShowMoreEntriesToLoadHint(int hiddenCount)
 302    {
 2303        loadMoreEntriesLabel.text = $"{hiddenCount} requests hidden. Scroll down to show more.";
 2304        loadMoreEntriesContainer.SetActive(true);
 2305        UpdateLayout();
 2306    }
 307
 1308    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 309
 7310    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 311
 312    private void UpdateEmptyOrFilledState()
 313    {
 8314        emptyStateContainer.SetActive(entries.Count == 0);
 8315        filledStateContainer.SetActive(entries.Count > 0);
 8316    }
 317
 318    private void OnSearchInputValueChanged(string friendUserName)
 319    {
 8320        if (!string.IsNullOrEmpty(friendUserName))
 0321            NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 8322    }
 323
 324    private void OnFriendRequestReceivedAccepted(FriendRequestEntry requestEntry)
 325    {
 0326        ShowFriendAcceptedNotification(requestEntry);
 0327        Remove(requestEntry.Model.userId);
 0328        OnFriendRequestApproved?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0329    }
 330
 331    private void ShowFriendAcceptedNotification(FriendRequestEntry requestEntry)
 332    {
 0333        acceptedFriendNotification.model.timer = NOTIFICATIONS_DURATION;
 0334        acceptedFriendNotification.model.groupID = NOTIFICATIONS_ID;
 0335        acceptedFriendNotification.model.message = $"You and {requestEntry.Model.userName} are now friends!";
 0336        NotificationsController.i?.ShowNotification(acceptedFriendNotification);
 0337    }
 338
 339    private void OnEntryRejectButtonPressed(FriendRequestEntry requestEntry)
 340    {
 0341        Remove(requestEntry.Model.userId);
 0342        OnRejectConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0343    }
 344
 345    private void OnEntryCancelButtonPressed(FriendRequestEntry requestEntry)
 346    {
 0347        Remove(requestEntry.Model.userId);
 0348        OnCancelConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0349    }
 350
 351    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 352    {
 0353        contextMenuPanel.Show(friendEntry.Model.userId);
 0354        contextMenuPanel.SetFriendshipContentActive(false);
 0355        friendEntry.Dock(contextMenuPanel);
 0356    }
 357
 358    private void UpdateCounterLabel()
 359    {
 8360        receivedRequestsCountText.SetText("RECEIVED ({0})", receivedRequestsList.Count());
 8361        sentRequestsCountText.SetText("SENT ({0})", sentRequestsList.Count());
 8362    }
 363
 364    private void HandleFriendBlockRequest(string userId, bool blockUser)
 365    {
 0366        var friendEntryToBlock = Get(userId);
 0367        if (friendEntryToBlock == null) return;
 368        // instantly refresh ui
 0369        friendEntryToBlock.Model.blocked = blockUser;
 0370        Set(userId, (FriendRequestEntryModel) friendEntryToBlock.Model);
 0371    }
 372
 373    private void FetchProfilePicturesForVisibleEntries()
 374    {
 1322375        foreach (var entry in entries.Values.Skip(currentAvatarSnapshotIndex).Take(AVATAR_SNAPSHOTS_PER_FRAME))
 376        {
 330377            if (entry.IsVisible(viewport))
 330378                entry.EnableAvatarSnapshotFetching();
 379            else
 0380                entry.DisableAvatarSnapshotFetching();
 381        }
 382
 331383        currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 384
 331385        if (currentAvatarSnapshotIndex >= entries.Count)
 331386            currentAvatarSnapshotIndex = 0;
 331387    }
 388
 389    private void SetQueuedEntries()
 390    {
 656391        if (creationQueue.Count == 0) return;
 392
 24393        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 394        {
 6395            var pair = creationQueue.FirstOrDefault();
 6396            creationQueue.Remove(pair.Key);
 6397            Set(pair.Key, pair.Value);
 398        }
 399
 6400        HideMoreFriendsLoadingSpinner();
 6401    }
 402
 403    private void RequestMoreEntries(Vector2 position)
 404    {
 1405        if (!loadMoreEntriesContainer.activeInHierarchy ||
 406            loadMoreEntriesSpinner.activeInHierarchy ||
 0407            (Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime) < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 408
 1409        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD && lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_TH
 410        {
 1411            if (requireMoreEntriesRoutine != null)
 0412                StopCoroutine(requireMoreEntriesRoutine);
 413
 1414            ShowMoreFriendsLoadingSpinner();
 1415            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 416
 1417            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 418        }
 419
 1420        lastScrollPosition = position;
 1421    }
 422
 423    private IEnumerator WaitThenRequireMoreEntries()
 424    {
 1425        yield return new WaitForSeconds(1f);
 1426        OnRequireMoreEntries?.Invoke();
 1427    }
 428
 429    [Serializable]
 430    private class Model
 431    {
 432        [Serializable]
 433        public struct UserIdAndEntry
 434        {
 435            public string userId;
 436            public bool received;
 437            public SerializableEntryModel model;
 438        }
 439
 440        [Serializable]
 441        public class SerializableEntryModel : FriendRequestEntryModel
 442        {
 443        }
 444
 445        public UserIdAndEntry[] friends;
 24446        public bool isReceivedRequestsExpanded = true;
 24447        public bool isSentRequestsExpanded = true;
 448    }
 449}