< Summary

Class:ChatHUDView
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDView.cs
Covered lines:53
Uncovered lines:143
Coverable lines:196
Total lines:418
Line coverage:27% (53 of 196)
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%220100%
OnEnable()0%110100%
OnDisable()0%110100%
Update()0%4.024088.89%
ResetInputField(...)0%6200%
RefreshControl()0%20400%
FocusInputField()0%110100%
UnfocusInputField()0%220100%
SetInputFieldText(...)0%2100%
ActivatePreview()0%2.152066.67%
DeactivatePreview()0%6200%
FadeOutMessages()0%2.262060%
SetFadeoutMode(...)0%20400%
AddEntry(...)0%20400%
SetEntry(...)0%20400%
RemoveFirstEntry()0%12300%
Hide(...)0%6200%
OnMessageCancelHover()0%2100%
ClearAllEntries()0%6200%
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%2100%
GetFirstEntry()0%12300%

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.Helpers;
 6using DCL.Interface;
 7using TMPro;
 8using UnityEngine;
 9using UnityEngine.Events;
 10using UnityEngine.EventSystems;
 11using UnityEngine.UI;
 12
 13public class ChatHUDView : BaseComponentView, IChatHUDComponentView
 14{
 15    private const string VIEW_PATH = "SocialBarV1/Chat";
 16
 17    public TMP_InputField inputField;
 18    public RectTransform chatEntriesContainer;
 19    public ScrollRect scrollRect;
 20    public GameObject messageHoverPanel;
 21    public GameObject messageHoverGotoPanel;
 22    public TextMeshProUGUI messageHoverText;
 23    public TextMeshProUGUI messageHoverGotoText;
 24    public UserContextMenu contextMenu;
 25    public UserContextConfirmationDialog confirmationDialog;
 26    [SerializeField] private DefaultChatEntryFactory defaultChatEntryFactory;
 27    [SerializeField] private Model model;
 28    [SerializeField] private InputAction_Trigger nextChatInHistoryInput;
 29    [SerializeField] private InputAction_Trigger previousChatInHistoryInput;
 30
 3131    private readonly Dictionary<string, ChatEntry> entries = new Dictionary<string, ChatEntry>();
 3132    private readonly ChatMessage currentMessage = new ChatMessage();
 33
 3134    private readonly Dictionary<Action, UnityAction<string>> inputFieldSelectedListeners =
 35        new Dictionary<Action, UnityAction<string>>();
 36
 3137    private readonly Dictionary<Action, UnityAction<string>> inputFieldUnselectedListeners =
 38        new Dictionary<Action, UnityAction<string>>();
 39
 40    private int updateLayoutDelayedFrames;
 41    private bool isSortingDirty;
 42
 043    protected bool IsFadeoutModeEnabled => model.enableFadeoutMode;
 44
 45    public event Action<string> OnMessageUpdated;
 46
 47    public event Action OnShowMenu
 48    {
 49        add
 50        {
 251            if (contextMenu != null)
 252                contextMenu.OnShowMenu += value;
 253        }
 54        remove
 55        {
 256            if (contextMenu != null)
 257                contextMenu.OnShowMenu -= value;
 258        }
 59    }
 60
 61    public event Action OnInputFieldSelected
 62    {
 63        add
 64        {
 065            void Action(string s) => value.Invoke();
 266            inputFieldSelectedListeners[value] = Action;
 267            inputField.onSelect.AddListener(Action);
 268        }
 69        remove
 70        {
 471            if (!inputFieldSelectedListeners.ContainsKey(value)) return;
 072            inputField.onSelect.RemoveListener(inputFieldSelectedListeners[value]);
 073            inputFieldSelectedListeners.Remove(value);
 074        }
 75    }
 76
 77    public event Action OnInputFieldDeselected
 78    {
 79        add
 80        {
 081            void Action(string s) => value.Invoke();
 282            inputFieldUnselectedListeners[value] = Action;
 283            inputField.onDeselect.AddListener(Action);
 284        }
 85        remove
 86        {
 487            if (!inputFieldUnselectedListeners.ContainsKey(value)) return;
 088            inputField.onDeselect.RemoveListener(inputFieldUnselectedListeners[value]);
 089            inputFieldUnselectedListeners.Remove(value);
 090        }
 91    }
 92
 93    public event Action OnPreviousChatInHistory;
 94    public event Action OnNextChatInHistory;
 95
 96    public event Action<ChatMessage> OnSendMessage;
 97
 098    public int EntryCount => entries.Count;
 099    public IChatEntryFactory ChatEntryFactory { get; set; }
 1100    public bool IsInputFieldSelected => inputField.isFocused;
 101
 102    public static ChatHUDView Create()
 103    {
 0104        var view = Instantiate(Resources.Load<GameObject>(VIEW_PATH)).GetComponent<ChatHUDView>();
 0105        return view;
 106    }
 107
 108    public override void Awake()
 109    {
 28110        base.Awake();
 28111        inputField.onSubmit.AddListener(OnInputFieldSubmit);
 28112        inputField.onSelect.AddListener(OnInputFieldSelect);
 28113        inputField.onDeselect.AddListener(OnInputFieldDeselect);
 28114        inputField.onValueChanged.AddListener(str => OnMessageUpdated?.Invoke(str));
 28115        ChatEntryFactory ??= defaultChatEntryFactory;
 28116        model.enableFadeoutMode = true;
 28117    }
 118
 119    public override void OnEnable()
 120    {
 29121        base.OnEnable();
 29122        UpdateLayout();
 29123        nextChatInHistoryInput.OnTriggered += HandleNextChatInHistoryInput;
 29124        previousChatInHistoryInput.OnTriggered += HandlePreviousChatInHistoryInput;
 29125    }
 126
 127    public override void OnDisable()
 128    {
 29129        base.OnDisable();
 29130        nextChatInHistoryInput.OnTriggered -= HandleNextChatInHistoryInput;
 29131        previousChatInHistoryInput.OnTriggered -= HandlePreviousChatInHistoryInput;
 29132    }
 133
 134    public override void Update()
 135    {
 897136        base.Update();
 137
 897138        if (updateLayoutDelayedFrames > 0)
 139        {
 18140            updateLayoutDelayedFrames--;
 141
 18142            if (updateLayoutDelayedFrames <= 0)
 4143                chatEntriesContainer.ForceUpdateLayout(delayed: false);
 144        }
 145
 897146        if (isSortingDirty)
 0147            SortEntriesImmediate();
 897148        isSortingDirty = false;
 897149    }
 150
 151    public void ResetInputField(bool loseFocus = false)
 152    {
 0153        inputField.text = string.Empty;
 0154        inputField.caretColor = Color.white;
 0155        if (loseFocus)
 0156            UnfocusInputField();
 0157    }
 158
 159    public override void RefreshControl()
 160    {
 0161        if (model.isInputFieldFocused)
 0162            FocusInputField();
 0163        SetInputFieldText(model.inputFieldText);
 0164        SetFadeoutMode(model.enableFadeoutMode);
 0165        if (model.isInPreviewMode)
 0166            ActivatePreview();
 167        else
 0168            DeactivatePreview();
 0169        ClearAllEntries();
 0170        foreach (var entry in model.entries)
 0171            AddEntry(entry);
 0172    }
 173
 174    public void FocusInputField()
 175    {
 1176        inputField.ActivateInputField();
 1177        inputField.Select();
 1178    }
 179
 1180    public void UnfocusInputField() => EventSystem.current?.SetSelectedGameObject(null);
 181
 182    public void SetInputFieldText(string text)
 183    {
 0184        model.inputFieldText = text;
 0185        inputField.text = text;
 0186        inputField.MoveTextEnd(false);
 0187    }
 188
 189    public void ActivatePreview()
 190    {
 2191        model.isInPreviewMode = true;
 192
 4193        foreach (var entry in entries.Values)
 0194            entry.ActivatePreview();
 2195    }
 196
 197    public void DeactivatePreview()
 198    {
 0199        model.isInPreviewMode = false;
 200
 0201        foreach (var entry in entries.Values)
 0202            entry.DeactivatePreview();
 0203    }
 204
 205    public void FadeOutMessages()
 206    {
 4207        foreach (var entry in entries.Values)
 0208            entry.FadeOut();
 2209    }
 210
 211    private void SetFadeoutMode(bool enabled)
 212    {
 0213        model.enableFadeoutMode = enabled;
 214
 0215        foreach (var entry in entries.Values)
 0216            entry.SetFadeout(enabled && IsEntryVisible(entry));
 217
 0218        if (enabled)
 0219            confirmationDialog.Hide();
 0220    }
 221
 222    public virtual void AddEntry(ChatEntryModel model, bool setScrollPositionToBottom = false)
 223    {
 0224        if (entries.ContainsKey(model.messageId))
 225        {
 0226            var chatEntry = entries[model.messageId];
 0227            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0228            chatEntry.Populate(model);
 229
 0230            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 0231        }
 232        else
 233        {
 0234            var chatEntry = ChatEntryFactory.Create(model);
 0235            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0236            chatEntry.Populate(model);
 237
 0238            if (model.messageType == ChatMessage.Type.PUBLIC
 239                || model.messageType == ChatMessage.Type.PRIVATE)
 0240                chatEntry.OnPressRightButton += OnOpenContextMenu;
 241
 0242            chatEntry.OnTriggerHover += OnMessageTriggerHover;
 0243            chatEntry.OnTriggerHoverGoto += OnMessageCoordinatesTriggerHover;
 0244            chatEntry.OnCancelHover += OnMessageCancelHover;
 0245            chatEntry.OnCancelGotoHover += OnMessageCancelGotoHover;
 246
 0247            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 248        }
 0249    }
 250
 251    public virtual void SetEntry(string messageId, ChatEntry chatEntry, bool setScrollPositionToBottom = false)
 252    {
 0253        chatEntry.transform.SetParent(chatEntriesContainer, false);
 0254        entries[messageId] = chatEntry;
 255
 0256        if (model.isInPreviewMode)
 0257            chatEntry.ActivatePreviewInstantly();
 258
 0259        SortEntries();
 0260        UpdateLayout();
 261
 0262        if (setScrollPositionToBottom && scrollRect.verticalNormalizedPosition > 0)
 0263            scrollRect.verticalNormalizedPosition = 0;
 0264    }
 265
 266    public void RemoveFirstEntry()
 267    {
 0268        if (entries.Count <= 0) return;
 0269        var firstEntry = GetFirstEntry();
 0270        if (firstEntry == null) return;
 0271        Destroy(firstEntry.gameObject);
 0272        entries.Remove(firstEntry.Model.messageId);
 0273    }
 274
 275    public override void Hide(bool instant = false)
 276    {
 0277        base.Hide(instant);
 0278        if (contextMenu == null) return;
 0279        contextMenu.Hide();
 0280        confirmationDialog.Hide();
 0281    }
 282
 283    public void OnMessageCancelHover()
 284    {
 0285        messageHoverPanel.SetActive(false);
 0286        messageHoverText.text = string.Empty;
 0287    }
 288
 289    public virtual void ClearAllEntries()
 290    {
 0291        foreach (var entry in entries.Values)
 0292            Destroy(entry.gameObject);
 0293        entries.Clear();
 0294    }
 295
 296    private bool IsEntryVisible(ChatEntry entry)
 297    {
 0298        int visibleCorners =
 299            (entry.transform as RectTransform).CountCornersVisibleFrom(scrollRect.viewport.transform as RectTransform);
 0300        return visibleCorners > 0;
 301    }
 302
 303    private void OnInputFieldSubmit(string message)
 304    {
 0305        currentMessage.body = message;
 0306        currentMessage.sender = string.Empty;
 0307        currentMessage.messageType = ChatMessage.Type.NONE;
 0308        currentMessage.recipient = string.Empty;
 309
 310        // A TMP_InputField is automatically marked as 'wasCanceled' when the ESC key is pressed
 0311        if (inputField.wasCanceled)
 0312            currentMessage.body = string.Empty;
 313
 314        // we have to wait one frame to disengage the flow triggered by OnSendMessage
 315        // otherwise it crashes the application (WebGL only) due a TextMeshPro bug
 0316        StartCoroutine(WaitThenTriggerSendMessage());
 0317    }
 318
 319    private IEnumerator WaitThenTriggerSendMessage()
 320    {
 0321        yield return null;
 0322        OnSendMessage?.Invoke(currentMessage);
 0323    }
 324
 325    private void OnInputFieldSelect(string message)
 326    {
 0327        AudioScriptableObjects.inputFieldFocus.Play(true);
 0328    }
 329
 330    private void OnInputFieldDeselect(string message)
 331    {
 0332        AudioScriptableObjects.inputFieldUnfocus.Play(true);
 0333    }
 334
 335    private void OnOpenContextMenu(DefaultChatEntry chatEntry)
 336    {
 0337        chatEntry.DockContextMenu((RectTransform) contextMenu.transform);
 0338        contextMenu.transform.parent = transform;
 0339        contextMenu.Show(chatEntry.Model.senderId);
 0340    }
 341
 342    protected virtual void OnMessageTriggerHover(DefaultChatEntry chatEntry)
 343    {
 0344        if (contextMenu == null || contextMenu.isVisible)
 0345            return;
 346
 0347        messageHoverText.text = chatEntry.messageLocalDateTime;
 0348        chatEntry.DockHoverPanel((RectTransform) messageHoverPanel.transform);
 0349        messageHoverPanel.SetActive(true);
 0350    }
 351
 352    private void OnMessageCoordinatesTriggerHover(DefaultChatEntry chatEntry, ParcelCoordinates parcelCoordinates)
 353    {
 0354        messageHoverGotoText.text = $"{parcelCoordinates} INFO";
 0355        chatEntry.DockHoverPanel((RectTransform) messageHoverGotoPanel.transform);
 0356        messageHoverGotoPanel.SetActive(true);
 0357    }
 358
 359    private void OnMessageCancelGotoHover()
 360    {
 0361        messageHoverGotoPanel.SetActive(false);
 0362        messageHoverGotoText.text = string.Empty;
 0363    }
 364
 0365    private void SortEntries() => isSortingDirty = true;
 366
 367    private void SortEntriesImmediate()
 368    {
 0369        if (this.entries.Count <= 0) return;
 370
 0371        var entries = this.entries.Values.OrderBy(x => x.Model.timestamp).ToList();
 372
 0373        for (var i = 0; i < entries.Count; i++)
 374        {
 0375            if (entries[i].transform.GetSiblingIndex() != i)
 0376                entries[i].transform.SetSiblingIndex(i);
 377        }
 0378    }
 379
 0380    private void HandleNextChatInHistoryInput(DCLAction_Trigger action) => OnNextChatInHistory?.Invoke();
 381
 0382    private void HandlePreviousChatInHistoryInput(DCLAction_Trigger action) => OnPreviousChatInHistory?.Invoke();
 383
 384    private void UpdateLayout()
 385    {
 386        // we have to wait several frames before updating the layout
 387        // every message entry waits for 3 frames before updating its own layout
 388        // we gotta force the layout updating after that
 389        // TODO: simplify this change to a bool when we update to a working TextMeshPro version
 0390        updateLayoutDelayedFrames = 4;
 0391    }
 392
 393    private ChatEntry GetFirstEntry()
 394    {
 0395        ChatEntry firstEntry = null;
 396
 0397        for (var i = 0; i < chatEntriesContainer.childCount; i++)
 398        {
 0399            var firstChildTransform = chatEntriesContainer.GetChild(i);
 0400            var entry = firstChildTransform.GetComponent<ChatEntry>();
 0401            if (entry == null) continue;
 0402            firstEntry = entry;
 0403            break;
 404        }
 405
 0406        return firstEntry;
 407    }
 408
 409    [Serializable]
 410    private struct Model
 411    {
 412        public bool isInPreviewMode;
 413        public bool isInputFieldFocused;
 414        public string inputFieldText;
 415        public bool enableFadeoutMode;
 416        public ChatEntryModel[] entries;
 417    }
 418}