< 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:146
Uncovered lines:69
Coverable lines:215
Total lines:455
Line coverage:67.9% (146 of 215)
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%2.012085.71%
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%110100%
CreateEntry(...)0%22094.44%
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%
OpenFriendRequestDetails(...)0%6200%
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
 1461    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<string> OnFriendRequestOpened;
 75    public event Action OnRequireMoreEntries;
 76
 77    public override void OnEnable()
 78    {
 779        base.OnEnable();
 780        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search a friend you want to add"});
 781        searchBar.OnSubmit += SendFriendRequest;
 782        searchBar.OnSearchText += OnSearchInputValueChanged;
 783        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 784        scroll.onValueChanged.AddListener(RequestMoreEntries);
 785        UpdateLayout();
 786    }
 87
 88    public override void OnDisable()
 89    {
 790        base.OnDisable();
 791        searchBar.OnSubmit -= SendFriendRequest;
 792        searchBar.OnSearchText -= OnSearchInputValueChanged;
 793        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 794        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 795        NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 096    }
 97
 98    public override void Update()
 99    {
 3311100        base.Update();
 101
 3311102        if (isLayoutDirty)
 8103            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 3311104        isLayoutDirty = false;
 105
 3311106        SetQueuedEntries();
 3311107        FetchProfilePicturesForVisibleEntries();
 3311108    }
 109
 110    public void Expand()
 111    {
 23112        receivedRequestsList.Expand();
 23113        sentRequestsList.Expand();
 23114    }
 115
 116    public void Show()
 117    {
 7118        gameObject.SetActive(true);
 7119        enabledHeader.SetActive(true);
 7120        disabledHeader.SetActive(false);
 7121        searchBar.ClearSearch();
 7122    }
 123
 124    public void Hide()
 125    {
 8126        gameObject.SetActive(false);
 8127        enabledHeader.SetActive(false);
 8128        disabledHeader.SetActive(true);
 8129        contextMenuPanel.Hide();
 8130    }
 131
 132    public override void RefreshControl()
 133    {
 0134        Clear();
 135
 0136        foreach (var friend in model.friends)
 0137            Set(friend.userId, friend.model);
 138
 0139        if (model.isReceivedRequestsExpanded)
 0140            receivedRequestsList.Expand();
 141        else
 0142            receivedRequestsList.Collapse();
 143
 0144        if (model.isSentRequestsExpanded)
 0145            sentRequestsList.Expand();
 146        else
 0147            sentRequestsList.Collapse();
 0148    }
 149
 150    public void Remove(string userId)
 151    {
 12152        if (creationQueue.ContainsKey(userId))
 0153            creationQueue.Remove(userId);
 154
 23155        if (!entries.ContainsKey(userId)) return;
 156
 1157        if (pooleableEntries.TryGetValue(userId, out var pooleableObject))
 158        {
 1159            entryPool.Release(pooleableObject);
 1160            pooleableEntries.Remove(userId);
 161        }
 162
 1163        entries.Remove(userId);
 1164        receivedRequestsList.Remove(userId);
 1165        sentRequestsList.Remove(userId);
 166
 1167        UpdateEmptyOrFilledState();
 1168        UpdateCounterLabel();
 1169        UpdateLayout();
 1170    }
 171
 172    public void Clear()
 173    {
 1174        HideMoreFriendsLoadingSpinner();
 1175        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1176        scroll.verticalNormalizedPosition = 1f;
 1177        creationQueue.Clear();
 1178        entries.ToList().ForEach(pair => Remove(pair.Key));
 1179        receivedRequestsList.Clear();
 1180        sentRequestsList.Clear();
 1181        UpdateEmptyOrFilledState();
 1182        UpdateCounterLabel();
 1183    }
 184
 37185    public FriendRequestEntry Get(string userId) => entries.ContainsKey(userId) ? entries[userId] : null;
 186
 6187    public void Enqueue(string userId, FriendRequestEntryModel model) => creationQueue[userId] = model;
 188
 189    public void Set(string userId, FriendRequestEntryModel model)
 190    {
 6191        if (creationQueue.ContainsKey(userId))
 192        {
 0193            creationQueue[userId] = model;
 0194            return;
 195        }
 196
 6197        if (!entries.ContainsKey(userId))
 198        {
 6199            CreateEntry(userId);
 6200            UpdateLayout();
 201        }
 202
 6203        Populate(userId, model);
 6204        UpdateEmptyOrFilledState();
 6205        UpdateCounterLabel();
 6206    }
 207
 208    public void Populate(string userId, FriendRequestEntryModel model)
 209    {
 6210        if (!entries.ContainsKey(userId))
 211        {
 0212            if (creationQueue.ContainsKey(userId))
 0213                creationQueue[userId] = model;
 0214            return;
 215        }
 216
 6217        var entry = entries[userId];
 6218        entry.Populate(model);
 219
 6220        if (model.isReceived)
 4221            receivedRequestsList.Add(userId, entry);
 222        else
 2223            sentRequestsList.Add(userId, entry);
 2224    }
 225
 226    public void ShowUserNotFoundNotification()
 227    {
 0228        friendSearchFailedNotification.model.timer = NOTIFICATIONS_DURATION;
 0229        friendSearchFailedNotification.model.groupID = NOTIFICATIONS_ID;
 0230        NotificationsController.i?.ShowNotification(friendSearchFailedNotification);
 0231    }
 232
 17233    private void UpdateLayout() => isLayoutDirty = true;
 234
 235    private void CreateEntry(string userId)
 236    {
 6237        entryPool = GetEntryPool();
 6238        var newFriendEntry = entryPool.Get();
 6239        pooleableEntries.Add(userId, newFriendEntry);
 6240        var entry = newFriendEntry.gameObject.GetComponent<FriendRequestEntry>();
 6241        if (entry == null) return;
 6242        entries.Add(userId, entry);
 243
 6244        entry.OnAccepted -= OnFriendRequestReceivedAccepted;
 6245        entry.OnAccepted += OnFriendRequestReceivedAccepted;
 6246        entry.OnRejected -= OnEntryRejectButtonPressed;
 6247        entry.OnRejected += OnEntryRejectButtonPressed;
 6248        entry.OnCancelled -= OnEntryCancelButtonPressed;
 6249        entry.OnCancelled += OnEntryCancelButtonPressed;
 6250        entry.OnMenuToggle -= OnEntryMenuToggle;
 6251        entry.OnMenuToggle += OnEntryMenuToggle;
 6252        entry.OnOpened -= OpenFriendRequestDetails;
 6253        entry.OnOpened += OpenFriendRequestDetails;
 6254    }
 255
 256    private Pool GetEntryPool()
 257    {
 6258        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 6259        if (entryPool != null) return entryPool;
 260
 6261        entryPool = PoolManager.i.AddPool(
 262            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 263            Instantiate(entryPrefab).gameObject,
 264            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 265            isPersistent: true);
 6266        entryPool.ForcePrewarm();
 267
 6268        return entryPool;
 269    }
 270
 271    private void SendFriendRequest(string friendUserName)
 272    {
 0273        friendUserName = friendUserName.Trim()
 274            .Replace("\n", "")
 275            .Replace("\r", "");
 0276        if (string.IsNullOrEmpty(friendUserName)) return;
 277
 0278        searchBar.ClearSearch();
 0279        lastRequestSentUserName = friendUserName;
 0280        OnFriendRequestSent?.Invoke(friendUserName);
 0281    }
 282
 283    public void ShowAlreadyFriendsNotification()
 284    {
 0285        alreadyFriendsNotification.model.timer = NOTIFICATIONS_DURATION;
 0286        alreadyFriendsNotification.model.groupID = NOTIFICATIONS_ID;
 0287        NotificationsController.i?.ShowNotification(alreadyFriendsNotification);
 0288    }
 289
 290    public void ShowRequestSuccessfullySentNotification()
 291    {
 0292        requestSentNotification.model.timer = NOTIFICATIONS_DURATION;
 0293        requestSentNotification.model.groupID = NOTIFICATIONS_ID;
 0294        requestSentNotification.model.message = $"Your request to {lastRequestSentUserName} successfully sent!";
 0295        NotificationsController.i?.ShowNotification(requestSentNotification);
 0296    }
 297
 298    public void HideMoreFriendsToLoadHint()
 299    {
 1300        loadMoreEntriesContainer.SetActive(false);
 1301        UpdateLayout();
 1302    }
 303
 304    public void ShowMoreEntriesToLoadHint(int hiddenCount)
 305    {
 2306        loadMoreEntriesLabel.text = $"{hiddenCount} requests hidden. Scroll down to show more.";
 2307        loadMoreEntriesContainer.SetActive(true);
 2308        UpdateLayout();
 2309    }
 310
 1311    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 312
 7313    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 314
 315    private void UpdateEmptyOrFilledState()
 316    {
 8317        emptyStateContainer.SetActive(entries.Count == 0);
 8318        filledStateContainer.SetActive(entries.Count > 0);
 8319    }
 320
 321    private void OnSearchInputValueChanged(string friendUserName)
 322    {
 8323        if (!string.IsNullOrEmpty(friendUserName))
 0324            NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 8325    }
 326
 327    private void OpenFriendRequestDetails(FriendRequestEntry entry) =>
 0328        OnFriendRequestOpened?.Invoke(entry.Model.userId);
 329
 330    private void OnFriendRequestReceivedAccepted(FriendRequestEntry requestEntry)
 331    {
 0332        ShowFriendAcceptedNotification(requestEntry);
 0333        Remove(requestEntry.Model.userId);
 0334        OnFriendRequestApproved?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0335    }
 336
 337    private void ShowFriendAcceptedNotification(FriendRequestEntry requestEntry)
 338    {
 0339        acceptedFriendNotification.model.timer = NOTIFICATIONS_DURATION;
 0340        acceptedFriendNotification.model.groupID = NOTIFICATIONS_ID;
 0341        acceptedFriendNotification.model.message = $"You and {requestEntry.Model.userName} are now friends!";
 0342        NotificationsController.i?.ShowNotification(acceptedFriendNotification);
 0343    }
 344
 345    private void OnEntryRejectButtonPressed(FriendRequestEntry requestEntry)
 346    {
 0347        Remove(requestEntry.Model.userId);
 0348        OnRejectConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0349    }
 350
 351    private void OnEntryCancelButtonPressed(FriendRequestEntry requestEntry)
 352    {
 0353        Remove(requestEntry.Model.userId);
 0354        OnCancelConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0355    }
 356
 357    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 358    {
 0359        contextMenuPanel.Show(friendEntry.Model.userId);
 0360        contextMenuPanel.SetFriendshipContentActive(false);
 0361        friendEntry.Dock(contextMenuPanel);
 0362    }
 363
 364    private void UpdateCounterLabel()
 365    {
 8366        receivedRequestsCountText.SetText("RECEIVED ({0})", receivedRequestsList.Count());
 8367        sentRequestsCountText.SetText("SENT ({0})", sentRequestsList.Count());
 8368    }
 369
 370    private void HandleFriendBlockRequest(string userId, bool blockUser)
 371    {
 0372        var friendEntryToBlock = Get(userId);
 0373        if (friendEntryToBlock == null) return;
 374        // instantly refresh ui
 0375        friendEntryToBlock.Model.blocked = blockUser;
 0376        Set(userId, (FriendRequestEntryModel) friendEntryToBlock.Model);
 0377    }
 378
 379    private void FetchProfilePicturesForVisibleEntries()
 380    {
 13242381        foreach (var entry in entries.Values.Skip(currentAvatarSnapshotIndex).Take(AVATAR_SNAPSHOTS_PER_FRAME))
 382        {
 3310383            if (entry.IsVisible(viewport))
 3310384                entry.EnableAvatarSnapshotFetching();
 385            else
 0386                entry.DisableAvatarSnapshotFetching();
 387        }
 388
 3311389        currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 390
 3311391        if (currentAvatarSnapshotIndex >= entries.Count)
 3311392            currentAvatarSnapshotIndex = 0;
 3311393    }
 394
 395    private void SetQueuedEntries()
 396    {
 6616397        if (creationQueue.Count == 0) return;
 398
 24399        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 400        {
 6401            var pair = creationQueue.FirstOrDefault();
 6402            creationQueue.Remove(pair.Key);
 6403            Set(pair.Key, pair.Value);
 404        }
 405
 6406        HideMoreFriendsLoadingSpinner();
 6407    }
 408
 409    private void RequestMoreEntries(Vector2 position)
 410    {
 1411        if (!loadMoreEntriesContainer.activeInHierarchy ||
 412            loadMoreEntriesSpinner.activeInHierarchy ||
 0413            (Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime) < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 414
 1415        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD && lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_TH
 416        {
 1417            if (requireMoreEntriesRoutine != null)
 0418                StopCoroutine(requireMoreEntriesRoutine);
 419
 1420            ShowMoreFriendsLoadingSpinner();
 1421            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 422
 1423            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 424        }
 425
 1426        lastScrollPosition = position;
 1427    }
 428
 429    private IEnumerator WaitThenRequireMoreEntries()
 430    {
 1431        yield return new WaitForSeconds(1f);
 1432        OnRequireMoreEntries?.Invoke();
 1433    }
 434
 435    [Serializable]
 436    private class Model
 437    {
 438        [Serializable]
 439        public struct UserIdAndEntry
 440        {
 441            public string userId;
 442            public bool received;
 443            public SerializableEntryModel model;
 444        }
 445
 446        [Serializable]
 447        public class SerializableEntryModel : FriendRequestEntryModel
 448        {
 449        }
 450
 451        public UserIdAndEntry[] friends;
 24452        public bool isReceivedRequestsExpanded = true;
 24453        public bool isSentRequestsExpanded = true;
 454    }
 455}