< 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:30
Uncovered lines:184
Coverable lines:214
Total lines:453
Line coverage:14% (30 of 214)
Covered branches:0
Total branches:0
Covered methods:9
Total methods:44
Method coverage:20.4% (9 of 44)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
FriendRequestsTabComponentView()0%110100%
OnEnable()0%2100%
OnDisable()0%6200%
Update()0%6200%
Expand()0%110100%
Show()0%2100%
Hide()0%2100%
RefreshControl()0%20400%
Remove(...)0%20400%
Clear()0%110100%
Get(...)0%6200%
Enqueue(...)0%2100%
Set(...)0%12300%
Populate(...)0%20400%
ShowUserNotFoundNotification()0%6200%
UpdateLayout()0%110100%
CreateEntry(...)0%6200%
GetEntryPool()0%6200%
SendFriendRequest(...)0%12300%
ShowAlreadyFriendsNotification()0%6200%
ShowRequestSuccessfullySentNotification()0%6200%
HideMoreFriendsToLoadHint()0%110100%
ShowMoreEntriesToLoadHint(...)0%2100%
ShowMoreFriendsLoadingSpinner()0%2100%
HideMoreFriendsLoadingSpinner()0%110100%
UpdateEmptyOrFilledState()0%110100%
OnSearchInputValueChanged(...)0%12300%
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%30500%
SetQueuedEntries()0%20400%
RequestMoreEntries(...)0%56700%
WaitThenRequireMoreEntries()0%20400%
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
 249    private readonly Dictionary<string, PoolableObject> pooleableEntries = new Dictionary<string, PoolableObject>();
 250    private readonly Dictionary<string, FriendRequestEntry> entries = new Dictionary<string, FriendRequestEntry>();
 251    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;
 257    private Vector2 lastScrollPosition = Vector2.one;
 58    private Coroutine requireMoreEntriesRoutine;
 59    private float loadMoreEntriesRestrictionTime;
 60
 061    public Dictionary<string, FriendRequestEntry> Entries => entries;
 62
 063    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    {
 079        base.OnEnable();
 080        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search a friend you want to add"});
 081        searchBar.OnSubmit += SendFriendRequest;
 082        searchBar.OnSearchText += OnSearchInputValueChanged;
 083        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 084        scroll.onValueChanged.AddListener(RequestMoreEntries);
 085        UpdateLayout();
 086    }
 87
 88    public override void OnDisable()
 89    {
 090        base.OnDisable();
 091        searchBar.OnSubmit -= SendFriendRequest;
 092        searchBar.OnSearchText -= OnSearchInputValueChanged;
 093        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 094        scroll.onValueChanged.RemoveListener(RequestMoreEntries);
 095        NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 096    }
 97
 98    public void Update()
 99    {
 0100        if (isLayoutDirty)
 0101            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 0102        isLayoutDirty = false;
 103
 0104        SetQueuedEntries();
 0105        FetchProfilePicturesForVisibleEntries();
 0106    }
 107
 108    public void Expand()
 109    {
 1110        receivedRequestsList.Expand();
 1111        sentRequestsList.Expand();
 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 override void RefreshControl()
 131    {
 0132        Clear();
 133
 0134        foreach (var friend in model.friends)
 0135            Set(friend.userId, friend.model);
 136
 0137        if (model.isReceivedRequestsExpanded)
 0138            receivedRequestsList.Expand();
 139        else
 0140            receivedRequestsList.Collapse();
 141
 0142        if (model.isSentRequestsExpanded)
 0143            sentRequestsList.Expand();
 144        else
 0145            sentRequestsList.Collapse();
 0146    }
 147
 148    public void Remove(string userId)
 149    {
 0150        if (creationQueue.ContainsKey(userId))
 0151            creationQueue.Remove(userId);
 152
 0153        if (!entries.ContainsKey(userId)) return;
 154
 0155        if (pooleableEntries.TryGetValue(userId, out var pooleableObject))
 156        {
 0157            entryPool.Release(pooleableObject);
 0158            pooleableEntries.Remove(userId);
 159        }
 160
 0161        entries.Remove(userId);
 0162        receivedRequestsList.Remove(userId);
 0163        sentRequestsList.Remove(userId);
 164
 0165        UpdateEmptyOrFilledState();
 0166        UpdateCounterLabel();
 0167        UpdateLayout();
 0168    }
 169
 170    public void Clear()
 171    {
 1172        HideMoreFriendsLoadingSpinner();
 1173        loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 1174        scroll.verticalNormalizedPosition = 1f;
 1175        creationQueue.Clear();
 1176        entries.ToList().ForEach(pair => Remove(pair.Key));
 1177        receivedRequestsList.Clear();
 1178        sentRequestsList.Clear();
 1179        UpdateEmptyOrFilledState();
 1180        UpdateCounterLabel();
 1181    }
 182
 0183    public FriendRequestEntry Get(string userId) => entries.ContainsKey(userId) ? entries[userId] : null;
 184
 0185    public void Enqueue(string userId, FriendRequestEntryModel model) => creationQueue[userId] = model;
 186
 187    public void Set(string userId, FriendRequestEntryModel model)
 188    {
 0189        if (creationQueue.ContainsKey(userId))
 190        {
 0191            creationQueue[userId] = model;
 0192            return;
 193        }
 194
 0195        if (!entries.ContainsKey(userId))
 196        {
 0197            CreateEntry(userId);
 0198            UpdateLayout();
 199        }
 200
 0201        Populate(userId, model);
 0202        UpdateEmptyOrFilledState();
 0203        UpdateCounterLabel();
 0204    }
 205
 206    public void Populate(string userId, FriendRequestEntryModel model)
 207    {
 0208        if (!entries.ContainsKey(userId))
 209        {
 0210            if (creationQueue.ContainsKey(userId))
 0211                creationQueue[userId] = model;
 0212            return;
 213        }
 214
 0215        var entry = entries[userId];
 0216        entry.Populate(model);
 217
 0218        if (model.isReceived)
 0219            receivedRequestsList.Add(userId, entry);
 220        else
 0221            sentRequestsList.Add(userId, entry);
 0222    }
 223
 224    public void ShowUserNotFoundNotification()
 225    {
 0226        friendSearchFailedNotification.model.timer = NOTIFICATIONS_DURATION;
 0227        friendSearchFailedNotification.model.groupID = NOTIFICATIONS_ID;
 0228        NotificationsController.i?.ShowNotification(friendSearchFailedNotification);
 0229    }
 230
 1231    private void UpdateLayout() => isLayoutDirty = true;
 232
 233    private void CreateEntry(string userId)
 234    {
 0235        entryPool = GetEntryPool();
 0236        var newFriendEntry = entryPool.Get();
 0237        pooleableEntries.Add(userId, newFriendEntry);
 0238        var entry = newFriendEntry.gameObject.GetComponent<FriendRequestEntry>();
 0239        if (entry == null) return;
 0240        entries.Add(userId, entry);
 241
 0242        entry.OnAccepted -= OnFriendRequestReceivedAccepted;
 0243        entry.OnAccepted += OnFriendRequestReceivedAccepted;
 0244        entry.OnRejected -= OnEntryRejectButtonPressed;
 0245        entry.OnRejected += OnEntryRejectButtonPressed;
 0246        entry.OnCancelled -= OnEntryCancelButtonPressed;
 0247        entry.OnCancelled += OnEntryCancelButtonPressed;
 0248        entry.OnMenuToggle -= OnEntryMenuToggle;
 0249        entry.OnMenuToggle += OnEntryMenuToggle;
 0250        entry.OnOpened -= OpenFriendRequestDetails;
 0251        entry.OnOpened += OpenFriendRequestDetails;
 0252    }
 253
 254    private Pool GetEntryPool()
 255    {
 0256        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 0257        if (entryPool != null) return entryPool;
 258
 0259        entryPool = PoolManager.i.AddPool(
 260            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 261            Instantiate(entryPrefab).gameObject,
 262            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 263            isPersistent: true);
 0264        entryPool.ForcePrewarm();
 265
 0266        return entryPool;
 267    }
 268
 269    private void SendFriendRequest(string friendUserName)
 270    {
 0271        friendUserName = friendUserName.Trim()
 272            .Replace("\n", "")
 273            .Replace("\r", "");
 0274        if (string.IsNullOrEmpty(friendUserName)) return;
 275
 0276        searchBar.ClearSearch();
 0277        lastRequestSentUserName = friendUserName;
 0278        OnFriendRequestSent?.Invoke(friendUserName);
 0279    }
 280
 281    public void ShowAlreadyFriendsNotification()
 282    {
 0283        alreadyFriendsNotification.model.timer = NOTIFICATIONS_DURATION;
 0284        alreadyFriendsNotification.model.groupID = NOTIFICATIONS_ID;
 0285        NotificationsController.i?.ShowNotification(alreadyFriendsNotification);
 0286    }
 287
 288    public void ShowRequestSuccessfullySentNotification()
 289    {
 0290        requestSentNotification.model.timer = NOTIFICATIONS_DURATION;
 0291        requestSentNotification.model.groupID = NOTIFICATIONS_ID;
 0292        requestSentNotification.model.message = $"Your request to {lastRequestSentUserName} successfully sent!";
 0293        NotificationsController.i?.ShowNotification(requestSentNotification);
 0294    }
 295
 296    public void HideMoreFriendsToLoadHint()
 297    {
 1298        loadMoreEntriesContainer.SetActive(false);
 1299        UpdateLayout();
 1300    }
 301
 302    public void ShowMoreEntriesToLoadHint(int hiddenCount)
 303    {
 0304        loadMoreEntriesLabel.text = $"{hiddenCount} requests hidden. Scroll down to show more.";
 0305        loadMoreEntriesContainer.SetActive(true);
 0306        UpdateLayout();
 0307    }
 308
 0309    private void ShowMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(true);
 310
 1311    private void HideMoreFriendsLoadingSpinner() => loadMoreEntriesSpinner.SetActive(false);
 312
 313    private void UpdateEmptyOrFilledState()
 314    {
 1315        emptyStateContainer.SetActive(entries.Count == 0);
 1316        filledStateContainer.SetActive(entries.Count > 0);
 1317    }
 318
 319    private void OnSearchInputValueChanged(string friendUserName)
 320    {
 0321        if (!string.IsNullOrEmpty(friendUserName))
 0322            NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 0323    }
 324
 325    private void OpenFriendRequestDetails(FriendRequestEntry entry) =>
 0326        OnFriendRequestOpened?.Invoke(entry.Model.userId);
 327
 328    private void OnFriendRequestReceivedAccepted(FriendRequestEntry requestEntry)
 329    {
 0330        ShowFriendAcceptedNotification(requestEntry);
 0331        Remove(requestEntry.Model.userId);
 0332        OnFriendRequestApproved?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0333    }
 334
 335    private void ShowFriendAcceptedNotification(FriendRequestEntry requestEntry)
 336    {
 0337        acceptedFriendNotification.model.timer = NOTIFICATIONS_DURATION;
 0338        acceptedFriendNotification.model.groupID = NOTIFICATIONS_ID;
 0339        acceptedFriendNotification.model.message = $"You and {requestEntry.Model.userName} are now friends!";
 0340        NotificationsController.i?.ShowNotification(acceptedFriendNotification);
 0341    }
 342
 343    private void OnEntryRejectButtonPressed(FriendRequestEntry requestEntry)
 344    {
 0345        Remove(requestEntry.Model.userId);
 0346        OnRejectConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0347    }
 348
 349    private void OnEntryCancelButtonPressed(FriendRequestEntry requestEntry)
 350    {
 0351        Remove(requestEntry.Model.userId);
 0352        OnCancelConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0353    }
 354
 355    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 356    {
 0357        friendEntry.Dock(contextMenuPanel);
 0358        contextMenuPanel.Show(friendEntry.Model.userId);
 0359        contextMenuPanel.SetFriendshipContentActive(false);
 0360    }
 361
 362    private void UpdateCounterLabel()
 363    {
 1364        receivedRequestsCountText.SetText("RECEIVED ({0})", receivedRequestsList.Count());
 1365        sentRequestsCountText.SetText("SENT ({0})", sentRequestsList.Count());
 1366    }
 367
 368    private void HandleFriendBlockRequest(string userId, bool blockUser)
 369    {
 0370        var friendEntryToBlock = Get(userId);
 0371        if (friendEntryToBlock == null) return;
 372        // instantly refresh ui
 0373        friendEntryToBlock.Model.blocked = blockUser;
 0374        Set(userId, (FriendRequestEntryModel) friendEntryToBlock.Model);
 0375    }
 376
 377    private void FetchProfilePicturesForVisibleEntries()
 378    {
 0379        foreach (var entry in entries.Values.Skip(currentAvatarSnapshotIndex).Take(AVATAR_SNAPSHOTS_PER_FRAME))
 380        {
 0381            if (entry.IsVisible(viewport))
 0382                entry.EnableAvatarSnapshotFetching();
 383            else
 0384                entry.DisableAvatarSnapshotFetching();
 385        }
 386
 0387        currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 388
 0389        if (currentAvatarSnapshotIndex >= entries.Count)
 0390            currentAvatarSnapshotIndex = 0;
 0391    }
 392
 393    private void SetQueuedEntries()
 394    {
 0395        if (creationQueue.Count == 0) return;
 396
 0397        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 398        {
 0399            var pair = creationQueue.FirstOrDefault();
 0400            creationQueue.Remove(pair.Key);
 0401            Set(pair.Key, pair.Value);
 402        }
 403
 0404        HideMoreFriendsLoadingSpinner();
 0405    }
 406
 407    private void RequestMoreEntries(Vector2 position)
 408    {
 0409        if (!loadMoreEntriesContainer.activeInHierarchy ||
 410            loadMoreEntriesSpinner.activeInHierarchy ||
 0411            (Time.realtimeSinceStartup - loadMoreEntriesRestrictionTime) < MIN_TIME_TO_REQUIRE_MORE_ENTRIES) return;
 412
 0413        if (position.y < REQUEST_MORE_ENTRIES_SCROLL_THRESHOLD && lastScrollPosition.y >= REQUEST_MORE_ENTRIES_SCROLL_TH
 414        {
 0415            if (requireMoreEntriesRoutine != null)
 0416                StopCoroutine(requireMoreEntriesRoutine);
 417
 0418            ShowMoreFriendsLoadingSpinner();
 0419            requireMoreEntriesRoutine = StartCoroutine(WaitThenRequireMoreEntries());
 420
 0421            loadMoreEntriesRestrictionTime = Time.realtimeSinceStartup;
 422        }
 423
 0424        lastScrollPosition = position;
 0425    }
 426
 427    private IEnumerator WaitThenRequireMoreEntries()
 428    {
 0429        yield return new WaitForSeconds(1f);
 0430        OnRequireMoreEntries?.Invoke();
 0431    }
 432
 433    [Serializable]
 434    private class Model
 435    {
 436        [Serializable]
 437        public struct UserIdAndEntry
 438        {
 439            public string userId;
 440            public bool received;
 441            public SerializableEntryModel model;
 442        }
 443
 444        [Serializable]
 445        public class SerializableEntryModel : FriendRequestEntryModel
 446        {
 447        }
 448
 449        public UserIdAndEntry[] friends;
 2450        public bool isReceivedRequestsExpanded = true;
 2451        public bool isSentRequestsExpanded = true;
 452    }
 453}