< 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:121
Uncovered lines:68
Coverable lines:189
Total lines:412
Line coverage:64% (121 of 189)
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%2100%
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%
ShowMoreFriendsToLoadHint(...)0%110100%
HideMoreFriendsToLoadHint()0%110100%
ShowMoreFriendsToLoadHint()0%110100%
UpdateEmptyOrFilledState()0%110100%
OnSearchInputValueChanged(...)0%3.333066.67%
OnFriendRequestReceivedAccepted(...)0%12300%
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%220100%
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.Generic;
 3using System.Linq;
 4using DCL;
 5using DCL.Helpers;
 6using TMPro;
 7using UnityEngine;
 8using UnityEngine.UI;
 9
 10public class FriendRequestsTabComponentView : BaseComponentView
 11{
 12    private const string FRIEND_ENTRIES_POOL_NAME_PREFIX = "FriendRequestEntriesPool_";
 13    private const string NOTIFICATIONS_ID = "Friends";
 14    private const int PRE_INSTANTIATED_ENTRIES = 25;
 15    private const float NOTIFICATIONS_DURATION = 3;
 16    private const int AVATAR_SNAPSHOTS_PER_FRAME = 5;
 17    private const int CREATION_AMOUNT_PER_FRAME = 5;
 18
 19    [SerializeField] private GameObject enabledHeader;
 20    [SerializeField] private GameObject disabledHeader;
 21    [SerializeField] private GameObject emptyStateContainer;
 22    [SerializeField] private GameObject filledStateContainer;
 23    [SerializeField] private FriendRequestEntry entryPrefab;
 24    [SerializeField] private CollapsableSortedFriendEntryList receivedRequestsList;
 25    [SerializeField] private CollapsableSortedFriendEntryList sentRequestsList;
 26    [SerializeField] private SearchBarComponentView searchBar;
 27    [SerializeField] private TMP_Text receivedRequestsCountText;
 28    [SerializeField] private TMP_Text sentRequestsCountText;
 29    [SerializeField] private UserContextMenu contextMenuPanel;
 30    [SerializeField] private RectTransform viewport;
 31
 32    [Header("Notifications")] [SerializeField]
 33    private Notification requestSentNotification;
 34
 35    [SerializeField] private Notification friendSearchFailedNotification;
 36    [SerializeField] private Notification acceptedFriendNotification;
 37    [SerializeField] private Notification alreadyFriendsNotification;
 38    [SerializeField] private Model model;
 39
 40    [Header("Load More Entries")]
 41    [SerializeField] internal Button loadMoreEntriesButton;
 42    [SerializeField] internal GameObject loadMoreEntriesContainer;
 43    [SerializeField] internal TMP_Text loadMoreEntriesLabel;
 44
 2445    private readonly Dictionary<string, PoolableObject> pooleableEntries = new Dictionary<string, PoolableObject>();
 2446    private readonly Dictionary<string, FriendRequestEntry> entries = new Dictionary<string, FriendRequestEntry>();
 2447    private readonly Dictionary<string, FriendRequestEntryModel> creationQueue =
 48        new Dictionary<string, FriendRequestEntryModel>();
 49    private Pool entryPool;
 50    private string lastRequestSentUserName;
 51    private int currentAvatarSnapshotIndex;
 52    private bool isLayoutDirty;
 53
 054    public Dictionary<string, FriendRequestEntry> Entries => entries;
 55
 1456    public int Count => Entries.Count + creationQueue.Keys.Count(s => !Entries.ContainsKey(s));
 57
 58    public event Action<FriendRequestEntryModel> OnCancelConfirmation;
 59    public event Action<FriendRequestEntryModel> OnRejectConfirmation;
 60    public event Action<FriendRequestEntryModel> OnFriendRequestApproved;
 61    public event Action<string> OnFriendRequestSent;
 62    public event Action OnRequireMoreEntries;
 63
 64    public override void OnEnable()
 65    {
 766        base.OnEnable();
 767        searchBar.Configure(new SearchBarComponentModel {placeHolderText = "Search a friend you want to add"});
 768        searchBar.OnSubmit += SendFriendRequest;
 769        searchBar.OnSearchText += OnSearchInputValueChanged;
 770        contextMenuPanel.OnBlock += HandleFriendBlockRequest;
 771        loadMoreEntriesButton.onClick.AddListener(RequestMoreEntries);
 772        UpdateLayout();
 773    }
 74
 75    public override void OnDisable()
 76    {
 777        base.OnDisable();
 778        searchBar.OnSubmit -= SendFriendRequest;
 779        searchBar.OnSearchText -= OnSearchInputValueChanged;
 780        contextMenuPanel.OnBlock -= HandleFriendBlockRequest;
 781        loadMoreEntriesButton.onClick.RemoveListener(RequestMoreEntries);
 782        NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 783    }
 84
 85    public override void Update()
 86    {
 687        base.Update();
 88
 689        if (isLayoutDirty)
 690            Utils.ForceRebuildLayoutImmediate((RectTransform) filledStateContainer.transform);
 691        isLayoutDirty = false;
 92
 693        SetQueuedEntries();
 694        FetchProfilePicturesForVisibleEntries();
 695    }
 96
 97    public void Expand()
 98    {
 2399        receivedRequestsList.Expand();
 23100        sentRequestsList.Expand();
 23101    }
 102
 103    public void Show()
 104    {
 7105        gameObject.SetActive(true);
 7106        enabledHeader.SetActive(true);
 7107        disabledHeader.SetActive(false);
 7108        searchBar.ClearSearch();
 7109    }
 110
 111    public void Hide()
 112    {
 8113        gameObject.SetActive(false);
 8114        enabledHeader.SetActive(false);
 8115        disabledHeader.SetActive(true);
 8116        contextMenuPanel.Hide();
 8117    }
 118
 119    public override void RefreshControl()
 120    {
 0121        Clear();
 122
 0123        foreach (var friend in model.friends)
 0124            Set(friend.userId, friend.model);
 125
 0126        if (model.isReceivedRequestsExpanded)
 0127            receivedRequestsList.Expand();
 128        else
 0129            receivedRequestsList.Collapse();
 130
 0131        if (model.isSentRequestsExpanded)
 0132            sentRequestsList.Expand();
 133        else
 0134            sentRequestsList.Collapse();
 0135    }
 136
 137    public void Remove(string userId)
 138    {
 11139        if (creationQueue.ContainsKey(userId))
 0140            creationQueue.Remove(userId);
 141
 21142        if (!entries.ContainsKey(userId)) return;
 143
 1144        if (pooleableEntries.TryGetValue(userId, out var pooleableObject))
 145        {
 1146            entryPool.Release(pooleableObject);
 1147            pooleableEntries.Remove(userId);
 148        }
 149
 1150        entries.Remove(userId);
 1151        receivedRequestsList.Remove(userId);
 1152        sentRequestsList.Remove(userId);
 153
 1154        UpdateEmptyOrFilledState();
 1155        UpdateCounterLabel();
 1156        UpdateLayout();
 1157    }
 158
 159    public void Clear()
 160    {
 0161        entries.ToList().ForEach(pair => Remove(pair.Key));
 0162        receivedRequestsList.Clear();
 0163        sentRequestsList.Clear();
 0164        UpdateEmptyOrFilledState();
 0165        UpdateCounterLabel();
 0166    }
 167
 37168    public FriendRequestEntry Get(string userId) => entries.ContainsKey(userId) ? entries[userId] : null;
 169
 5170    public void Enqueue(string userId, FriendRequestEntryModel model) => creationQueue[userId] = model;
 171
 172    public void Set(string userId, FriendRequestEntryModel model)
 173    {
 5174        if (creationQueue.ContainsKey(userId))
 175        {
 0176            creationQueue[userId] = model;
 0177            return;
 178        }
 179
 5180        if (!entries.ContainsKey(userId))
 181        {
 5182            CreateEntry(userId);
 5183            UpdateLayout();
 184        }
 185
 5186        Populate(userId, model);
 5187        UpdateEmptyOrFilledState();
 5188        UpdateCounterLabel();
 5189    }
 190
 191    public void Populate(string userId, FriendRequestEntryModel model)
 192    {
 5193        if (!entries.ContainsKey(userId))
 194        {
 0195            if (creationQueue.ContainsKey(userId))
 0196                creationQueue[userId] = model;
 0197            return;
 198        }
 199
 5200        var entry = entries[userId];
 5201        entry.Populate(model);
 202
 5203        if (model.isReceived)
 3204            receivedRequestsList.Add(userId, entry);
 205        else
 2206            sentRequestsList.Add(userId, entry);
 2207    }
 208
 209    public void ShowUserNotFoundNotification()
 210    {
 0211        friendSearchFailedNotification.model.timer = NOTIFICATIONS_DURATION;
 0212        friendSearchFailedNotification.model.groupID = NOTIFICATIONS_ID;
 0213        NotificationsController.i?.ShowNotification(friendSearchFailedNotification);
 0214    }
 215
 0216    private void UpdateLayout() => isLayoutDirty = true;
 217
 218    private void CreateEntry(string userId)
 219    {
 5220        entryPool = GetEntryPool();
 5221        var newFriendEntry = entryPool.Get();
 5222        pooleableEntries.Add(userId, newFriendEntry);
 5223        var entry = newFriendEntry.gameObject.GetComponent<FriendRequestEntry>();
 5224        if (entry == null) return;
 5225        entries.Add(userId, entry);
 226
 5227        entry.OnAccepted -= OnFriendRequestReceivedAccepted;
 5228        entry.OnAccepted += OnFriendRequestReceivedAccepted;
 5229        entry.OnRejected -= OnEntryRejectButtonPressed;
 5230        entry.OnRejected += OnEntryRejectButtonPressed;
 5231        entry.OnCancelled -= OnEntryCancelButtonPressed;
 5232        entry.OnCancelled += OnEntryCancelButtonPressed;
 5233        entry.OnMenuToggle -= OnEntryMenuToggle;
 5234        entry.OnMenuToggle += OnEntryMenuToggle;
 5235    }
 236
 237    private Pool GetEntryPool()
 238    {
 5239        var entryPool = PoolManager.i.GetPool(FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID());
 5240        if (entryPool != null) return entryPool;
 241
 5242        entryPool = PoolManager.i.AddPool(
 243            FRIEND_ENTRIES_POOL_NAME_PREFIX + name + GetInstanceID(),
 244            Instantiate(entryPrefab).gameObject,
 245            maxPrewarmCount: PRE_INSTANTIATED_ENTRIES,
 246            isPersistent: true);
 5247        entryPool.ForcePrewarm();
 248
 5249        return entryPool;
 250    }
 251
 252    private void SendFriendRequest(string friendUserName)
 253    {
 0254        if (string.IsNullOrEmpty(friendUserName)) return;
 255
 0256        searchBar.ClearSearch();
 0257        lastRequestSentUserName = friendUserName;
 0258        OnFriendRequestSent?.Invoke(friendUserName);
 0259    }
 260
 261    public void ShowAlreadyFriendsNotification()
 262    {
 0263        alreadyFriendsNotification.model.timer = NOTIFICATIONS_DURATION;
 0264        alreadyFriendsNotification.model.groupID = NOTIFICATIONS_ID;
 0265        NotificationsController.i?.ShowNotification(alreadyFriendsNotification);
 0266    }
 267
 268    public void ShowRequestSuccessfullySentNotification()
 269    {
 0270        requestSentNotification.model.timer = NOTIFICATIONS_DURATION;
 0271        requestSentNotification.model.groupID = NOTIFICATIONS_ID;
 0272        requestSentNotification.model.message = $"Your request to {lastRequestSentUserName} successfully sent!";
 0273        NotificationsController.i?.ShowNotification(requestSentNotification);
 0274    }
 275
 276    public void ShowMoreFriendsToLoadHint(int pendingFriendsCount)
 277    {
 1278        loadMoreEntriesLabel.SetText(
 279            $"{pendingFriendsCount} request hidden. Click below to show more.");
 1280        ShowMoreFriendsToLoadHint();
 1281    }
 282
 283    public void HideMoreFriendsToLoadHint()
 284    {
 1285        loadMoreEntriesContainer.SetActive(false);
 1286        UpdateLayout();
 1287    }
 288
 289    private void ShowMoreFriendsToLoadHint()
 290    {
 1291        loadMoreEntriesContainer.SetActive(true);
 1292        UpdateLayout();
 1293    }
 294
 295    private void UpdateEmptyOrFilledState()
 296    {
 6297        emptyStateContainer.SetActive(entries.Count == 0);
 6298        filledStateContainer.SetActive(entries.Count > 0);
 6299    }
 300
 301    private void OnSearchInputValueChanged(string friendUserName)
 302    {
 8303        if (!string.IsNullOrEmpty(friendUserName))
 0304            NotificationsController.i?.DismissAllNotifications(NOTIFICATIONS_ID);
 8305    }
 306
 307    private void OnFriendRequestReceivedAccepted(FriendRequestEntry requestEntry)
 308    {
 309        // Add placeholder friend to avoid affecting UX by roundtrip with kernel
 0310        FriendsController.i?.UpdateFriendshipStatus(new FriendsController.FriendshipUpdateStatusMessage
 311        {
 312            userId = requestEntry.Model.userId,
 313            action = FriendshipAction.APPROVED
 314        });
 315
 0316        ShowFriendAcceptedNotification(requestEntry);
 0317        Remove(requestEntry.Model.userId);
 0318        OnFriendRequestApproved?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0319    }
 320
 321    private void ShowFriendAcceptedNotification(FriendRequestEntry requestEntry)
 322    {
 0323        acceptedFriendNotification.model.timer = NOTIFICATIONS_DURATION;
 0324        acceptedFriendNotification.model.groupID = NOTIFICATIONS_ID;
 0325        acceptedFriendNotification.model.message = $"You and {requestEntry.Model.userName} are now friends!";
 0326        NotificationsController.i?.ShowNotification(acceptedFriendNotification);
 0327    }
 328
 329    private void OnEntryRejectButtonPressed(FriendRequestEntry requestEntry)
 330    {
 0331        Remove(requestEntry.Model.userId);
 0332        OnRejectConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0333    }
 334
 335    private void OnEntryCancelButtonPressed(FriendRequestEntry requestEntry)
 336    {
 0337        Remove(requestEntry.Model.userId);
 0338        OnCancelConfirmation?.Invoke((FriendRequestEntryModel) requestEntry.Model);
 0339    }
 340
 341    private void OnEntryMenuToggle(FriendEntryBase friendEntry)
 342    {
 0343        contextMenuPanel.Show(friendEntry.Model.userId);
 0344        friendEntry.Dock(contextMenuPanel);
 0345    }
 346
 347    private void UpdateCounterLabel()
 348    {
 6349        receivedRequestsCountText.SetText("RECEIVED ({0})", receivedRequestsList.Count());
 6350        sentRequestsCountText.SetText("SENT ({0})", sentRequestsList.Count());
 6351    }
 352
 353    private void HandleFriendBlockRequest(string userId, bool blockUser)
 354    {
 0355        var friendEntryToBlock = Get(userId);
 0356        if (friendEntryToBlock == null) return;
 357        // instantly refresh ui
 0358        friendEntryToBlock.Model.blocked = blockUser;
 0359        Set(userId, (FriendRequestEntryModel) friendEntryToBlock.Model);
 0360    }
 361
 362    private void FetchProfilePicturesForVisibleEntries()
 363    {
 22364        foreach (var entry in entries.Values.Skip(currentAvatarSnapshotIndex).Take(AVATAR_SNAPSHOTS_PER_FRAME))
 365        {
 5366            if (entry.IsVisible(viewport))
 5367                entry.EnableAvatarSnapshotFetching();
 368            else
 0369                entry.DisableAvatarSnapshotFetching();
 370        }
 371
 6372        currentAvatarSnapshotIndex += AVATAR_SNAPSHOTS_PER_FRAME;
 373
 6374        if (currentAvatarSnapshotIndex >= entries.Count)
 6375            currentAvatarSnapshotIndex = 0;
 6376    }
 377
 378    private void SetQueuedEntries()
 379    {
 7380        if (creationQueue.Count == 0) return;
 381
 20382        for (var i = 0; i < CREATION_AMOUNT_PER_FRAME && creationQueue.Count != 0; i++)
 383        {
 5384            var pair = creationQueue.FirstOrDefault();
 5385            creationQueue.Remove(pair.Key);
 5386            Set(pair.Key, pair.Value);
 387        }
 5388    }
 389
 1390    private void RequestMoreEntries() => OnRequireMoreEntries?.Invoke();
 391
 392    [Serializable]
 393    private class Model
 394    {
 395        [Serializable]
 396        public struct UserIdAndEntry
 397        {
 398            public string userId;
 399            public bool received;
 400            public SerializableEntryModel model;
 401        }
 402
 403        [Serializable]
 404        public class SerializableEntryModel : FriendRequestEntryModel
 405        {
 406        }
 407
 408        public UserIdAndEntry[] friends;
 24409        public bool isReceivedRequestsExpanded = true;
 24410        public bool isSentRequestsExpanded = true;
 411    }
 412}