< 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:134
Coverable lines:187
Total lines:407
Line coverage:28.3% (53 of 187)
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.312057.14%
DeactivatePreview()0%6200%
FadeOutMessages()0%2.52050%
SetFadeoutMode(...)0%20400%
AddEntry(...)0%56700%
RemoveFirstEntry()0%6200%
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%20400%
HandleNextChatInHistoryInput(...)0%6200%
HandlePreviousChatInHistoryInput(...)0%6200%
UpdateLayout()0%2100%

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
 2731    [NonSerialized] protected List<ChatEntry> entries = new List<ChatEntry>();
 32
 2733    private readonly ChatMessage currentMessage = new ChatMessage();
 2734    private readonly Dictionary<Action, UnityAction<string>> inputFieldSelectedListeners =
 35        new Dictionary<Action, UnityAction<string>>();
 2736    private readonly Dictionary<Action, UnityAction<string>> inputFieldUnselectedListeners =
 37        new Dictionary<Action, UnityAction<string>>();
 38
 39    private int updateLayoutDelayedFrames;
 40    private bool isSortingDirty;
 41
 042    protected bool IsFadeoutModeEnabled => model.enableFadeoutMode;
 43
 44    public event Action<string> OnMessageUpdated;
 45
 46    public event Action OnShowMenu
 47    {
 48        add
 49        {
 250            if (contextMenu != null)
 251                contextMenu.OnShowMenu += value;
 252        }
 53        remove
 54        {
 255            if (contextMenu != null)
 256                contextMenu.OnShowMenu -= value;
 257        }
 58    }
 59
 60    public event Action OnInputFieldSelected
 61    {
 62        add
 63        {
 064            void Action(string s) => value.Invoke();
 265            inputFieldSelectedListeners[value] = Action;
 266            inputField.onSelect.AddListener(Action);
 267        }
 68        remove
 69        {
 470            if (!inputFieldSelectedListeners.ContainsKey(value)) return;
 071            inputField.onSelect.RemoveListener(inputFieldSelectedListeners[value]);
 072            inputFieldSelectedListeners.Remove(value);
 073        }
 74    }
 75
 76    public event Action OnInputFieldDeselected
 77    {
 78        add
 79        {
 080            void Action(string s) => value.Invoke();
 281            inputFieldUnselectedListeners[value] = Action;
 282            inputField.onDeselect.AddListener(Action);
 283        }
 84        remove
 85        {
 486            if (!inputFieldUnselectedListeners.ContainsKey(value)) return;
 087            inputField.onDeselect.RemoveListener(inputFieldUnselectedListeners[value]);
 088            inputFieldUnselectedListeners.Remove(value);
 089        }
 90    }
 91
 92    public event Action OnPreviousChatInHistory;
 93    public event Action OnNextChatInHistory;
 94
 95    public event Action<ChatMessage> OnSendMessage;
 96
 097    public int EntryCount => entries.Count;
 098    public IChatEntryFactory ChatEntryFactory { get; set; }
 199    public bool IsInputFieldSelected => inputField.isFocused;
 100
 101    public static ChatHUDView Create()
 102    {
 0103        var view = Instantiate(Resources.Load<GameObject>(VIEW_PATH)).GetComponent<ChatHUDView>();
 0104        return view;
 105    }
 106
 107    public override void Awake()
 108    {
 24109        base.Awake();
 24110        inputField.onSubmit.AddListener(OnInputFieldSubmit);
 24111        inputField.onSelect.AddListener(OnInputFieldSelect);
 24112        inputField.onDeselect.AddListener(OnInputFieldDeselect);
 24113        inputField.onValueChanged.AddListener(str => OnMessageUpdated?.Invoke(str));
 24114        ChatEntryFactory ??= defaultChatEntryFactory;
 24115        model.enableFadeoutMode = true;
 24116    }
 117
 118    public override void OnEnable()
 119    {
 25120        base.OnEnable();
 25121        UpdateLayout();
 25122        nextChatInHistoryInput.OnTriggered += HandleNextChatInHistoryInput;
 25123        previousChatInHistoryInput.OnTriggered += HandlePreviousChatInHistoryInput;
 25124    }
 125
 126    public override void OnDisable()
 127    {
 25128        base.OnDisable();
 25129        nextChatInHistoryInput.OnTriggered -= HandleNextChatInHistoryInput;
 25130        previousChatInHistoryInput.OnTriggered -= HandlePreviousChatInHistoryInput;
 25131    }
 132
 133    public override void Update()
 134    {
 851135        base.Update();
 136
 851137        if (updateLayoutDelayedFrames > 0)
 138        {
 18139            updateLayoutDelayedFrames--;
 140
 18141            if (updateLayoutDelayedFrames <= 0)
 4142                chatEntriesContainer.ForceUpdateLayout(delayed: false);
 143        }
 144
 851145        if (isSortingDirty)
 0146            SortEntriesImmediate();
 851147        isSortingDirty = false;
 851148    }
 149
 150    public void ResetInputField(bool loseFocus = false)
 151    {
 0152        inputField.text = string.Empty;
 0153        inputField.caretColor = Color.white;
 0154        if (loseFocus)
 0155            UnfocusInputField();
 0156    }
 157
 158    public override void RefreshControl()
 159    {
 0160        if (model.isInputFieldFocused)
 0161            FocusInputField();
 0162        SetInputFieldText(model.inputFieldText);
 0163        SetFadeoutMode(model.enableFadeoutMode);
 0164        if (model.isInPreviewMode)
 0165            ActivatePreview();
 166        else
 0167            DeactivatePreview();
 0168        ClearAllEntries();
 0169        foreach (var entry in model.entries)
 0170            AddEntry(entry);
 0171    }
 172
 173    public void FocusInputField()
 174    {
 1175        inputField.ActivateInputField();
 1176        inputField.Select();
 1177    }
 178
 1179    public void UnfocusInputField() => EventSystem.current?.SetSelectedGameObject(null);
 180
 181    public void SetInputFieldText(string text)
 182    {
 0183        model.inputFieldText = text;
 0184        inputField.text = text;
 0185        inputField.MoveTextEnd(false);
 0186    }
 187
 188    public void ActivatePreview()
 189    {
 2190        model.isInPreviewMode = true;
 191
 4192        for (var i = 0; i < entries.Count; i++)
 193        {
 0194            var entry = entries[i];
 0195            entry.ActivatePreview();
 196        }
 2197    }
 198    public void DeactivatePreview()
 199    {
 0200        model.isInPreviewMode = false;
 201
 0202        for (var i = 0; i < entries.Count; i++)
 203        {
 0204            var entry = entries[i];
 0205            entry.DeactivatePreview();
 206        }
 0207    }
 208    public void FadeOutMessages()
 209    {
 4210        for (var i = 0; i < entries.Count; i++)
 211        {
 0212            var entry = entries[i];
 0213            entry.FadeOut();
 214        }
 2215    }
 216
 217    private void SetFadeoutMode(bool enabled)
 218    {
 0219        model.enableFadeoutMode = enabled;
 220
 0221        for (int i = 0; i < entries.Count; i++)
 222        {
 0223            var entry = entries[i];
 224
 0225            if (enabled)
 226            {
 0227                entry.SetFadeout(IsEntryVisible(entry));
 0228            }
 229            else
 230            {
 0231                entry.SetFadeout(false);
 232            }
 233        }
 234
 0235        if (enabled)
 236        {
 0237            confirmationDialog.Hide();
 238        }
 0239    }
 240
 241    public virtual void AddEntry(ChatEntryModel model, bool setScrollPositionToBottom = false)
 242    {
 0243        var chatEntry = ChatEntryFactory.Create(model);
 0244        chatEntry.transform.SetParent(chatEntriesContainer, false);
 245
 0246        if(this.model.enableFadeoutMode)
 0247            chatEntry.SetFadeout(true);
 248        else
 0249            chatEntry.SetFadeout(false);
 250
 0251        chatEntry.Populate(model);
 252
 0253        if (model.messageType == ChatMessage.Type.PUBLIC
 254            || model.messageType == ChatMessage.Type.PRIVATE)
 0255            chatEntry.OnPressRightButton += OnOpenContextMenu;
 256
 0257        chatEntry.OnTriggerHover += OnMessageTriggerHover;
 0258        chatEntry.OnTriggerHoverGoto += OnMessageCoordinatesTriggerHover;
 0259        chatEntry.OnCancelHover += OnMessageCancelHover;
 0260        chatEntry.OnCancelGotoHover += OnMessageCancelGotoHover;
 261
 0262        entries.Add(chatEntry);
 263
 0264        if (this.model.isInPreviewMode)
 0265            chatEntry.ActivatePreviewInstantly();
 266
 0267        SortEntries();
 0268        UpdateLayout();
 269
 0270        if (setScrollPositionToBottom && scrollRect.verticalNormalizedPosition > 0)
 0271            scrollRect.verticalNormalizedPosition = 0;
 0272    }
 273
 274    public void RemoveFirstEntry()
 275    {
 0276        if (entries.Count <= 0) return;
 0277        Destroy(entries[0].gameObject);
 0278        entries.Remove(entries[0]);
 0279    }
 280
 281    public override void Hide(bool instant = false)
 282    {
 0283        base.Hide(instant);
 0284        if (contextMenu == null) return;
 0285        contextMenu.Hide();
 0286        confirmationDialog.Hide();
 0287    }
 288
 289    public void OnMessageCancelHover()
 290    {
 0291        messageHoverPanel.SetActive(false);
 0292        messageHoverText.text = string.Empty;
 0293    }
 294
 295    public virtual void ClearAllEntries()
 296    {
 0297        foreach (var entry in entries)
 0298            Destroy(entry.gameObject);
 0299        entries.Clear();
 0300    }
 301
 302    private bool IsEntryVisible(ChatEntry entry)
 303    {
 0304        int visibleCorners =
 305            (entry.transform as RectTransform).CountCornersVisibleFrom(scrollRect.viewport.transform as RectTransform);
 0306        return visibleCorners > 0;
 307    }
 308
 309    private void OnInputFieldSubmit(string message)
 310    {
 0311        currentMessage.body = message;
 0312        currentMessage.sender = string.Empty;
 0313        currentMessage.messageType = ChatMessage.Type.NONE;
 0314        currentMessage.recipient = string.Empty;
 315
 316        // A TMP_InputField is automatically marked as 'wasCanceled' when the ESC key is pressed
 0317        if (inputField.wasCanceled)
 0318            currentMessage.body = string.Empty;
 319
 320        // we have to wait one frame to disengage the flow triggered by OnSendMessage
 321        // otherwise it crashes the application (WebGL only) due a TextMeshPro bug
 0322        StartCoroutine(WaitThenTriggerSendMessage());
 0323    }
 324
 325    private IEnumerator WaitThenTriggerSendMessage()
 326    {
 0327        yield return null;
 0328        OnSendMessage?.Invoke(currentMessage);
 0329    }
 330
 331    private void OnInputFieldSelect(string message)
 332    {
 0333        AudioScriptableObjects.inputFieldFocus.Play(true);
 0334    }
 335
 336    private void OnInputFieldDeselect(string message)
 337    {
 0338        AudioScriptableObjects.inputFieldUnfocus.Play(true);
 0339    }
 340
 341    private void OnOpenContextMenu(DefaultChatEntry chatEntry)
 342    {
 0343        chatEntry.DockContextMenu((RectTransform) contextMenu.transform);
 0344        contextMenu.transform.parent = transform;
 0345        contextMenu.Show(chatEntry.Model.senderId);
 0346    }
 347
 348    protected virtual void OnMessageTriggerHover(DefaultChatEntry chatEntry)
 349    {
 0350        if (contextMenu == null || contextMenu.isVisible)
 0351            return;
 352
 0353        messageHoverText.text = chatEntry.messageLocalDateTime;
 0354        chatEntry.DockHoverPanel((RectTransform) messageHoverPanel.transform);
 0355        messageHoverPanel.SetActive(true);
 0356    }
 357
 358    private void OnMessageCoordinatesTriggerHover(DefaultChatEntry chatEntry, ParcelCoordinates parcelCoordinates)
 359    {
 0360        messageHoverGotoText.text = $"{parcelCoordinates} INFO";
 0361        chatEntry.DockHoverPanel((RectTransform) messageHoverGotoPanel.transform);
 0362        messageHoverGotoPanel.SetActive(true);
 0363    }
 364
 365    private void OnMessageCancelGotoHover()
 366    {
 0367        messageHoverGotoPanel.SetActive(false);
 0368        messageHoverGotoText.text = string.Empty;
 0369    }
 370
 0371    private void SortEntries() => isSortingDirty = true;
 372
 373    private void SortEntriesImmediate()
 374    {
 0375        entries = entries.OrderBy(x => x.Model.timestamp).ToList();
 376
 0377        int count = entries.Count;
 0378        for (int i = 0; i < count; i++)
 379        {
 0380            if (entries[i].transform.GetSiblingIndex() != i)
 0381                entries[i].transform.SetSiblingIndex(i);
 382        }
 0383    }
 384
 0385    private void HandleNextChatInHistoryInput(DCLAction_Trigger action) => OnNextChatInHistory?.Invoke();
 386
 0387    private void HandlePreviousChatInHistoryInput(DCLAction_Trigger action) => OnPreviousChatInHistory?.Invoke();
 388
 389    private void UpdateLayout()
 390    {
 391        // we have to wait several frames before updating the layout
 392        // every message entry waits for 3 frames before updating its own layout
 393        // we gotta force the layout updating after that
 394        // TODO: simplify this change to a bool when we update to a working TextMeshPro version
 0395        updateLayoutDelayedFrames = 4;
 0396    }
 397
 398    [Serializable]
 399    private struct Model
 400    {
 401        public bool isInPreviewMode;
 402        public bool isInputFieldFocused;
 403        public string inputFieldText;
 404        public bool enableFadeoutMode;
 405        public ChatEntryModel[] entries;
 406    }
 407}