< Summary

Class:ChatHUDView
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDView.cs
Covered lines:55
Uncovered lines:128
Coverable lines:183
Total lines:388
Line coverage:30% (55 of 183)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ChatHUDView()0%110100%
add_OnShowMenu(...)0%220100%
remove_OnShowMenu(...)0%220100%
add_OnInputFieldSelected(...)0%110100%
remove_OnInputFieldSelected(...)0%2.862040%
add_OnInputFieldDeselected(...)0%110100%
remove_OnInputFieldDeselected(...)0%2.862040%
Create()0%2100%
Awake()0%330100%
OnEnable()0%110100%
OnDisable()0%110100%
Update()0%4.184077.78%
ResetInputField(...)0%6200%
RefreshControl()0%12300%
FocusInputField()0%110100%
UnfocusInputField()0%220100%
SetInputFieldText(...)0%2100%
SetFadeoutMode(...)0%20400%
AddEntry(...)0%12300%
SetEntry(...)0%12300%
RemoveOldestEntry()0%12300%
Hide(...)0%6200%
OnMessageCancelHover()0%2100%
ClearAllEntries()0%2.092071.43%
IsEntryVisible(...)0%2100%
OnInputFieldSubmit(...)0%6200%
WaitThenTriggerSendMessage()0%20400%
OnInputFieldSelect(...)0%2100%
OnInputFieldDeselect(...)0%2100%
OnOpenContextMenu(...)0%2100%
OnMessageTriggerHover(...)0%12300%
OnMessageCoordinatesTriggerHover(...)0%2100%
OnMessageCancelGotoHover()0%2100%
SortEntries()0%2100%
SortEntriesImmediate()0%30500%
HandleNextChatInHistoryInput(...)0%6200%
HandlePreviousChatInHistoryInput(...)0%6200%
UpdateLayout()0%110100%
GetFirstEntry()0%20400%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDView.cs

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Linq;
 5using DCL.Chat.HUD;
 6using DCL.Helpers;
 7using DCL.Interface;
 8using TMPro;
 9using UnityEngine;
 10using UnityEngine.Events;
 11using UnityEngine.EventSystems;
 12using UnityEngine.UI;
 13
 14public class ChatHUDView : BaseComponentView, IChatHUDComponentView
 15{
 16    private const string VIEW_PATH = "SocialBarV1/Chat";
 17
 18    public TMP_InputField inputField;
 19    public RectTransform chatEntriesContainer;
 20    public ScrollRect scrollRect;
 21    public GameObject messageHoverPanel;
 22    public GameObject messageHoverGotoPanel;
 23    public TextMeshProUGUI messageHoverText;
 24    public TextMeshProUGUI messageHoverGotoText;
 25    public UserContextMenu contextMenu;
 26    public UserContextConfirmationDialog confirmationDialog;
 27    [SerializeField] private DefaultChatEntryFactory defaultChatEntryFactory;
 28    [SerializeField] private PoolChatEntryFactory poolChatEntryFactory;
 29    [SerializeField] private Model model;
 30    [SerializeField] private InputAction_Trigger nextChatInHistoryInput;
 31    [SerializeField] private InputAction_Trigger previousChatInHistoryInput;
 32
 3233    private readonly Dictionary<string, ChatEntry> entries = new Dictionary<string, ChatEntry>();
 3234    private readonly ChatMessage currentMessage = new ChatMessage();
 35
 3236    private readonly Dictionary<Action, UnityAction<string>> inputFieldSelectedListeners =
 37        new Dictionary<Action, UnityAction<string>>();
 38
 3239    private readonly Dictionary<Action, UnityAction<string>> inputFieldUnselectedListeners =
 40        new Dictionary<Action, UnityAction<string>>();
 41
 42    private int updateLayoutDelayedFrames;
 43    private bool isSortingDirty;
 44
 045    protected bool IsFadeoutModeEnabled => model.enableFadeoutMode;
 46
 47    public event Action<string> OnMessageUpdated;
 48
 49    public event Action OnShowMenu
 50    {
 51        add
 52        {
 353            if (contextMenu != null)
 354                contextMenu.OnShowMenu += value;
 355        }
 56        remove
 57        {
 358            if (contextMenu != null)
 359                contextMenu.OnShowMenu -= value;
 360        }
 61    }
 62
 63    public event Action OnInputFieldSelected
 64    {
 65        add
 66        {
 067            void Action(string s) => value.Invoke();
 368            inputFieldSelectedListeners[value] = Action;
 369            inputField.onSelect.AddListener(Action);
 370        }
 71        remove
 72        {
 373            if (!inputFieldSelectedListeners.ContainsKey(value))
 374                return;
 075            inputField.onSelect.RemoveListener(inputFieldSelectedListeners[value]);
 076            inputFieldSelectedListeners.Remove(value);
 077        }
 78    }
 79
 80    public event Action OnInputFieldDeselected
 81    {
 82        add
 83        {
 084            void Action(string s) => value.Invoke();
 385            inputFieldUnselectedListeners[value] = Action;
 386            inputField.onDeselect.AddListener(Action);
 387        }
 88        remove
 89        {
 390            if (!inputFieldUnselectedListeners.ContainsKey(value))
 391                return;
 092            inputField.onDeselect.RemoveListener(inputFieldUnselectedListeners[value]);
 093            inputFieldUnselectedListeners.Remove(value);
 094        }
 95    }
 96
 97    public event Action OnPreviousChatInHistory;
 98    public event Action OnNextChatInHistory;
 99    public event Action<ChatMessage> OnSendMessage;
 100
 0101    public int EntryCount => entries.Count;
 66102    public IChatEntryFactory ChatEntryFactory { get; set; }
 103
 104    public static ChatHUDView Create()
 105    {
 0106        var view = Instantiate(Resources.Load<GameObject>(VIEW_PATH)).GetComponent<ChatHUDView>();
 0107        return view;
 108    }
 109
 110    public override void Awake()
 111    {
 26112        base.Awake();
 26113        inputField.onSubmit.AddListener(OnInputFieldSubmit);
 26114        inputField.onSelect.AddListener(OnInputFieldSelect);
 26115        inputField.onDeselect.AddListener(OnInputFieldDeselect);
 26116        inputField.onValueChanged.AddListener(str => OnMessageUpdated?.Invoke(str));
 26117        ChatEntryFactory ??= (IChatEntryFactory) poolChatEntryFactory ?? defaultChatEntryFactory;
 26118        model.enableFadeoutMode = true;
 26119    }
 120
 121    public override void OnEnable()
 122    {
 29123        base.OnEnable();
 29124        UpdateLayout();
 29125        nextChatInHistoryInput.OnTriggered += HandleNextChatInHistoryInput;
 29126        previousChatInHistoryInput.OnTriggered += HandlePreviousChatInHistoryInput;
 29127    }
 128
 129    public override void OnDisable()
 130    {
 29131        base.OnDisable();
 29132        nextChatInHistoryInput.OnTriggered -= HandleNextChatInHistoryInput;
 29133        previousChatInHistoryInput.OnTriggered -= HandlePreviousChatInHistoryInput;
 29134    }
 135
 136    public override void Update()
 137    {
 3138        base.Update();
 139
 3140        if (updateLayoutDelayedFrames > 0)
 141        {
 3142            updateLayoutDelayedFrames--;
 143
 3144            if (updateLayoutDelayedFrames <= 0)
 0145                chatEntriesContainer.ForceUpdateLayout(delayed: false);
 146        }
 147
 3148        if (isSortingDirty)
 0149            SortEntriesImmediate();
 3150        isSortingDirty = false;
 3151    }
 152
 153    public void ResetInputField(bool loseFocus = false)
 154    {
 0155        inputField.text = string.Empty;
 0156        inputField.caretColor = Color.white;
 0157        if (loseFocus)
 0158            UnfocusInputField();
 0159    }
 160
 161    public override void RefreshControl()
 162    {
 0163        if (model.isInputFieldFocused)
 0164            FocusInputField();
 0165        SetInputFieldText(model.inputFieldText);
 0166        SetFadeoutMode(model.enableFadeoutMode);
 0167        ClearAllEntries();
 0168        foreach (var entry in model.entries)
 0169            AddEntry(entry);
 0170    }
 171
 172    public void FocusInputField()
 173    {
 3174        inputField.ActivateInputField();
 3175        inputField.Select();
 3176    }
 177
 3178    public void UnfocusInputField() => EventSystem.current?.SetSelectedGameObject(null);
 179
 180    public void SetInputFieldText(string text)
 181    {
 0182        model.inputFieldText = text;
 0183        inputField.text = text;
 0184        inputField.MoveTextEnd(false);
 0185    }
 186
 187    private void SetFadeoutMode(bool enabled)
 188    {
 0189        model.enableFadeoutMode = enabled;
 190
 0191        foreach (var entry in entries.Values)
 0192            entry.SetFadeout(enabled && IsEntryVisible(entry));
 193
 0194        if (enabled)
 0195            confirmationDialog.Hide();
 0196    }
 197
 198    public virtual void AddEntry(ChatEntryModel model, bool setScrollPositionToBottom = false)
 199    {
 0200        if (entries.ContainsKey(model.messageId))
 201        {
 0202            var chatEntry = entries[model.messageId];
 0203            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0204            chatEntry.Populate(model);
 205
 0206            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 207        }
 208        else
 209        {
 0210            var chatEntry = ChatEntryFactory.Create(model);
 0211            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0212            chatEntry.Populate(model);
 213
 0214            if (model.subType.Equals(ChatEntryModel.SubType.RECEIVED))
 0215                chatEntry.OnUserNameClicked += OnOpenContextMenu;
 216
 0217            chatEntry.OnTriggerHover += OnMessageTriggerHover;
 0218            chatEntry.OnTriggerHoverGoto += OnMessageCoordinatesTriggerHover;
 0219            chatEntry.OnCancelHover += OnMessageCancelHover;
 0220            chatEntry.OnCancelGotoHover += OnMessageCancelGotoHover;
 221
 0222            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 223        }
 0224    }
 225
 226    public virtual void SetEntry(string messageId, ChatEntry chatEntry, bool setScrollPositionToBottom = false)
 227    {
 0228        chatEntry.transform.SetParent(chatEntriesContainer, false);
 0229        entries[messageId] = chatEntry;
 230
 0231        SortEntries();
 0232        UpdateLayout();
 233
 0234        if (setScrollPositionToBottom && scrollRect.verticalNormalizedPosition > 0)
 0235            scrollRect.verticalNormalizedPosition = 0;
 0236    }
 237
 238    public void RemoveOldestEntry()
 239    {
 0240        if (entries.Count <= 0) return;
 0241        var firstEntry = GetFirstEntry();
 0242        if (!firstEntry) return;
 0243        entries.Remove(firstEntry.Model.messageId);
 0244        ChatEntryFactory.Destroy(firstEntry);
 0245        UpdateLayout();
 0246    }
 247
 248    public override void Hide(bool instant = false)
 249    {
 0250        base.Hide(instant);
 0251        if (contextMenu == null)
 0252            return;
 0253        contextMenu.Hide();
 0254        confirmationDialog.Hide();
 0255    }
 256
 257    public void OnMessageCancelHover()
 258    {
 0259        messageHoverPanel.SetActive(false);
 0260        messageHoverText.text = string.Empty;
 0261    }
 262
 263    public virtual void ClearAllEntries()
 264    {
 2265        foreach (var entry in entries.Values)
 0266            ChatEntryFactory.Destroy(entry);
 1267        entries.Clear();
 1268        UpdateLayout();
 1269    }
 270
 271    private bool IsEntryVisible(ChatEntry entry)
 272    {
 0273        int visibleCorners =
 274            (entry.transform as RectTransform).CountCornersVisibleFrom(scrollRect.viewport.transform as RectTransform);
 0275        return visibleCorners > 0;
 276    }
 277
 278    private void OnInputFieldSubmit(string message)
 279    {
 0280        currentMessage.body = message;
 0281        currentMessage.sender = string.Empty;
 0282        currentMessage.messageType = ChatMessage.Type.NONE;
 0283        currentMessage.recipient = string.Empty;
 284
 285        // A TMP_InputField is automatically marked as 'wasCanceled' when the ESC key is pressed
 0286        if (inputField.wasCanceled)
 0287            currentMessage.body = string.Empty;
 288
 289        // we have to wait one frame to disengage the flow triggered by OnSendMessage
 290        // otherwise it crashes the application (WebGL only) due a TextMeshPro bug
 0291        StartCoroutine(WaitThenTriggerSendMessage());
 0292    }
 293
 294    private IEnumerator WaitThenTriggerSendMessage()
 295    {
 0296        yield return null;
 0297        OnSendMessage?.Invoke(currentMessage);
 0298    }
 299
 0300    private void OnInputFieldSelect(string message) { AudioScriptableObjects.inputFieldFocus.Play(true); }
 301
 0302    private void OnInputFieldDeselect(string message) { AudioScriptableObjects.inputFieldUnfocus.Play(true); }
 303
 304    private void OnOpenContextMenu(ChatEntry chatEntry)
 305    {
 0306        chatEntry.DockContextMenu((RectTransform) contextMenu.transform);
 0307        contextMenu.transform.parent = transform.parent;
 0308        contextMenu.transform.SetAsLastSibling();
 0309        contextMenu.Show(chatEntry.Model.senderId);
 0310    }
 311
 312    private void OnMessageTriggerHover(ChatEntry chatEntry)
 313    {
 0314        if (contextMenu == null || contextMenu.isVisible)
 0315            return;
 316
 0317        messageHoverText.text = chatEntry.DateString;
 0318        chatEntry.DockHoverPanel((RectTransform) messageHoverPanel.transform);
 0319        messageHoverPanel.SetActive(true);
 0320    }
 321
 322    private void OnMessageCoordinatesTriggerHover(ChatEntry chatEntry, ParcelCoordinates parcelCoordinates)
 323    {
 0324        messageHoverGotoText.text = $"{parcelCoordinates} INFO";
 0325        chatEntry.DockHoverPanel((RectTransform) messageHoverGotoPanel.transform);
 0326        messageHoverGotoPanel.SetActive(true);
 0327    }
 328
 329    private void OnMessageCancelGotoHover()
 330    {
 0331        messageHoverGotoPanel.SetActive(false);
 0332        messageHoverGotoText.text = string.Empty;
 0333    }
 334
 0335    private void SortEntries() => isSortingDirty = true;
 336
 337    private void SortEntriesImmediate()
 338    {
 0339        if (this.entries.Count <= 0) return;
 340
 0341        var entries = this.entries.Values.OrderBy(x => x.Model.timestamp).ToList();
 342
 0343        for (var i = 0; i < entries.Count; i++)
 344        {
 0345            if (entries[i].transform.GetSiblingIndex() != i)
 0346                entries[i].transform.SetSiblingIndex(i);
 347        }
 0348    }
 349
 0350    private void HandleNextChatInHistoryInput(DCLAction_Trigger action) => OnNextChatInHistory?.Invoke();
 351
 0352    private void HandlePreviousChatInHistoryInput(DCLAction_Trigger action) => OnPreviousChatInHistory?.Invoke();
 353
 354    private void UpdateLayout()
 355    {
 356        // we have to wait several frames before updating the layout
 357        // every message entry waits for 3 frames before updating its own layout
 358        // we gotta force the layout updating after that
 359        // TODO: simplify this change to a bool when we update to a working TextMeshPro version
 30360        updateLayoutDelayedFrames = 4;
 30361    }
 362
 363    private ChatEntry GetFirstEntry()
 364    {
 0365        ChatEntry firstEntry = null;
 366
 0367        for (var i = 0; i < chatEntriesContainer.childCount; i++)
 368        {
 0369            var firstChildTransform = chatEntriesContainer.GetChild(i);
 0370            if (!firstChildTransform) continue;
 0371            var entry = firstChildTransform.GetComponent<ChatEntry>();
 0372            if (!entry) continue;
 0373            firstEntry = entry;
 0374            break;
 375        }
 376
 0377        return firstEntry;
 378    }
 379
 380    [Serializable]
 381    private struct Model
 382    {
 383        public bool isInputFieldFocused;
 384        public string inputFieldText;
 385        public bool enableFadeoutMode;
 386        public ChatEntryModel[] entries;
 387    }
 388}