< Summary

Class:ChatHUDView
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDView.cs
Covered lines:52
Uncovered lines:134
Coverable lines:186
Total lines:394
Line coverage:27.9% (52 of 186)
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.184077.78%
ResetInputField(...)0%6200%
RefreshControl()0%12300%
FocusInputField()0%110100%
UnfocusInputField()0%220100%
SetInputFieldText(...)0%2100%
FadeOutMessages()0%6200%
SetFadeoutMode(...)0%20400%
AddEntry(...)0%20400%
SetEntry(...)0%12300%
RemoveFirstEntry()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%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;
 12using System.Threading;
 13using Cysharp.Threading.Tasks;
 14using DG.Tweening;
 15
 16public class ChatHUDView : BaseComponentView, IChatHUDComponentView
 17{
 18    private const string VIEW_PATH = "SocialBarV1/Chat";
 19
 20    public TMP_InputField inputField;
 21    public RectTransform chatEntriesContainer;
 22    public ScrollRect scrollRect;
 23    public GameObject messageHoverPanel;
 24    public GameObject messageHoverGotoPanel;
 25    public TextMeshProUGUI messageHoverText;
 26    public TextMeshProUGUI messageHoverGotoText;
 27    public UserContextMenu contextMenu;
 28    public UserContextConfirmationDialog confirmationDialog;
 29    [SerializeField] private DefaultChatEntryFactory defaultChatEntryFactory;
 30    [SerializeField] private Model model;
 31    [SerializeField] private InputAction_Trigger nextChatInHistoryInput;
 32    [SerializeField] private InputAction_Trigger previousChatInHistoryInput;
 33
 3134    private readonly Dictionary<string, ChatEntry> entries = new Dictionary<string, ChatEntry>();
 3135    private readonly ChatMessage currentMessage = new ChatMessage();
 36
 3137    private readonly Dictionary<Action, UnityAction<string>> inputFieldSelectedListeners =
 38        new Dictionary<Action, UnityAction<string>>();
 39
 3140    private readonly Dictionary<Action, UnityAction<string>> inputFieldUnselectedListeners =
 41        new Dictionary<Action, UnityAction<string>>();
 42
 43    private int updateLayoutDelayedFrames;
 44    private bool isSortingDirty;
 45
 046    protected bool IsFadeoutModeEnabled => model.enableFadeoutMode;
 47
 48    public event Action<string> OnMessageUpdated;
 49
 50    public event Action OnShowMenu
 51    {
 52        add
 53        {
 354            if (contextMenu != null)
 355                contextMenu.OnShowMenu += value;
 356        }
 57        remove
 58        {
 359            if (contextMenu != null)
 360                contextMenu.OnShowMenu -= value;
 361        }
 62    }
 63
 64    public event Action OnInputFieldSelected
 65    {
 66        add
 67        {
 068            void Action(string s) => value.Invoke();
 369            inputFieldSelectedListeners[value] = Action;
 370            inputField.onSelect.AddListener(Action);
 371        }
 72        remove
 73        {
 374            if (!inputFieldSelectedListeners.ContainsKey(value))
 375                return;
 076            inputField.onSelect.RemoveListener(inputFieldSelectedListeners[value]);
 077            inputFieldSelectedListeners.Remove(value);
 078        }
 79    }
 80
 81    public event Action OnInputFieldDeselected
 82    {
 83        add
 84        {
 085            void Action(string s) => value.Invoke();
 386            inputFieldUnselectedListeners[value] = Action;
 387            inputField.onDeselect.AddListener(Action);
 388        }
 89        remove
 90        {
 391            if (!inputFieldUnselectedListeners.ContainsKey(value))
 392                return;
 093            inputField.onDeselect.RemoveListener(inputFieldUnselectedListeners[value]);
 094            inputFieldUnselectedListeners.Remove(value);
 095        }
 96    }
 97
 98    public event Action OnPreviousChatInHistory;
 99    public event Action OnNextChatInHistory;
 100    public event Action<ChatMessage> OnSendMessage;
 101
 0102    public int EntryCount => entries.Count;
 0103    public IChatEntryFactory ChatEntryFactory { get; set; }
 104
 105    public static ChatHUDView Create()
 106    {
 0107        var view = Instantiate(Resources.Load<GameObject>(VIEW_PATH)).GetComponent<ChatHUDView>();
 0108        return view;
 109    }
 110
 111    public override void Awake()
 112    {
 26113        base.Awake();
 26114        inputField.onSubmit.AddListener(OnInputFieldSubmit);
 26115        inputField.onSelect.AddListener(OnInputFieldSelect);
 26116        inputField.onDeselect.AddListener(OnInputFieldDeselect);
 26117        inputField.onValueChanged.AddListener(str => OnMessageUpdated?.Invoke(str));
 26118        ChatEntryFactory ??= defaultChatEntryFactory;
 26119        model.enableFadeoutMode = true;
 26120    }
 121
 122    public override void OnEnable()
 123    {
 29124        base.OnEnable();
 29125        UpdateLayout();
 29126        nextChatInHistoryInput.OnTriggered += HandleNextChatInHistoryInput;
 29127        previousChatInHistoryInput.OnTriggered += HandlePreviousChatInHistoryInput;
 29128    }
 129
 130    public override void OnDisable()
 131    {
 29132        base.OnDisable();
 29133        nextChatInHistoryInput.OnTriggered -= HandleNextChatInHistoryInput;
 29134        previousChatInHistoryInput.OnTriggered -= HandlePreviousChatInHistoryInput;
 29135    }
 136
 137    public override void Update()
 138    {
 3139        base.Update();
 140
 3141        if (updateLayoutDelayedFrames > 0)
 142        {
 3143            updateLayoutDelayedFrames--;
 144
 3145            if (updateLayoutDelayedFrames <= 0)
 0146                chatEntriesContainer.ForceUpdateLayout(delayed: false);
 147        }
 148
 3149        if (isSortingDirty)
 0150            SortEntriesImmediate();
 3151        isSortingDirty = false;
 3152    }
 153
 154    public void ResetInputField(bool loseFocus = false)
 155    {
 0156        inputField.text = string.Empty;
 0157        inputField.caretColor = Color.white;
 0158        if (loseFocus)
 0159            UnfocusInputField();
 0160    }
 161
 162    public override void RefreshControl()
 163    {
 0164        if (model.isInputFieldFocused)
 0165            FocusInputField();
 0166        SetInputFieldText(model.inputFieldText);
 0167        SetFadeoutMode(model.enableFadeoutMode);
 0168        ClearAllEntries();
 0169        foreach (var entry in model.entries)
 0170            AddEntry(entry);
 0171    }
 172
 173    public void FocusInputField()
 174    {
 2175        inputField.ActivateInputField();
 2176        inputField.Select();
 2177    }
 178
 3179    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 FadeOutMessages()
 189    {
 0190        foreach (var entry in entries.Values)
 0191            entry.FadeOut();
 0192    }
 193
 194    private void SetFadeoutMode(bool enabled)
 195    {
 0196        model.enableFadeoutMode = enabled;
 197
 0198        foreach (var entry in entries.Values)
 0199            entry.SetFadeout(enabled && IsEntryVisible(entry));
 200
 0201        if (enabled)
 0202            confirmationDialog.Hide();
 0203    }
 204
 205    public virtual void AddEntry(ChatEntryModel model, bool setScrollPositionToBottom = false)
 206    {
 0207        if (entries.ContainsKey(model.messageId))
 208        {
 0209            var chatEntry = entries[model.messageId];
 0210            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0211            chatEntry.Populate(model);
 212
 0213            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 0214        }
 215        else
 216        {
 0217            var chatEntry = ChatEntryFactory.Create(model);
 0218            chatEntry.SetFadeout(this.model.enableFadeoutMode);
 0219            chatEntry.Populate(model);
 220
 0221            if (chatEntry.showUserName && model.subType.Equals(ChatEntryModel.SubType.RECEIVED))
 0222                chatEntry.OnUserNameClicked += OnOpenContextMenu;
 223
 0224            chatEntry.OnTriggerHover += OnMessageTriggerHover;
 0225            chatEntry.OnTriggerHoverGoto += OnMessageCoordinatesTriggerHover;
 0226            chatEntry.OnCancelHover += OnMessageCancelHover;
 0227            chatEntry.OnCancelGotoHover += OnMessageCancelGotoHover;
 228
 0229            SetEntry(model.messageId, chatEntry, setScrollPositionToBottom);
 230        }
 0231    }
 232
 233    public virtual void SetEntry(string messageId, ChatEntry chatEntry, bool setScrollPositionToBottom = false)
 234    {
 0235        chatEntry.transform.SetParent(chatEntriesContainer, false);
 0236        entries[messageId] = chatEntry;
 237
 0238        SortEntries();
 0239        UpdateLayout();
 240
 0241        if (setScrollPositionToBottom && scrollRect.verticalNormalizedPosition > 0)
 0242            scrollRect.verticalNormalizedPosition = 0;
 0243    }
 244
 245    public void RemoveFirstEntry()
 246    {
 0247        if (entries.Count <= 0) return;
 0248        var firstEntry = GetFirstEntry();
 0249        if (firstEntry == null) return;
 0250        Destroy(firstEntry.gameObject);
 0251        entries.Remove(firstEntry.Model.messageId);
 0252        UpdateLayout();
 0253    }
 254
 255    public override void Hide(bool instant = false)
 256    {
 0257        base.Hide(instant);
 0258        if (contextMenu == null)
 0259            return;
 0260        contextMenu.Hide();
 0261        confirmationDialog.Hide();
 0262    }
 263
 264    public void OnMessageCancelHover()
 265    {
 0266        messageHoverPanel.SetActive(false);
 0267        messageHoverText.text = string.Empty;
 0268    }
 269
 270    public virtual void ClearAllEntries()
 271    {
 2272        foreach (var entry in entries.Values)
 0273            Destroy(entry.gameObject);
 1274        entries.Clear();
 1275        UpdateLayout();
 1276    }
 277
 278    private bool IsEntryVisible(ChatEntry entry)
 279    {
 0280        int visibleCorners =
 281            (entry.transform as RectTransform).CountCornersVisibleFrom(scrollRect.viewport.transform as RectTransform);
 0282        return visibleCorners > 0;
 283    }
 284
 285    private void OnInputFieldSubmit(string message)
 286    {
 0287        currentMessage.body = message;
 0288        currentMessage.sender = string.Empty;
 0289        currentMessage.messageType = ChatMessage.Type.NONE;
 0290        currentMessage.recipient = string.Empty;
 291
 292        // A TMP_InputField is automatically marked as 'wasCanceled' when the ESC key is pressed
 0293        if (inputField.wasCanceled)
 0294            currentMessage.body = string.Empty;
 295
 296        // we have to wait one frame to disengage the flow triggered by OnSendMessage
 297        // otherwise it crashes the application (WebGL only) due a TextMeshPro bug
 0298        StartCoroutine(WaitThenTriggerSendMessage());
 0299    }
 300
 301    private IEnumerator WaitThenTriggerSendMessage()
 302    {
 0303        yield return null;
 0304        OnSendMessage?.Invoke(currentMessage);
 0305    }
 306
 0307    private void OnInputFieldSelect(string message) { AudioScriptableObjects.inputFieldFocus.Play(true); }
 308
 0309    private void OnInputFieldDeselect(string message) { AudioScriptableObjects.inputFieldUnfocus.Play(true); }
 310
 311    private void OnOpenContextMenu(DefaultChatEntry chatEntry)
 312    {
 0313        chatEntry.DockContextMenu((RectTransform) contextMenu.transform);
 0314        contextMenu.transform.parent = transform.parent;
 0315        contextMenu.transform.SetAsLastSibling();
 0316        contextMenu.Show(chatEntry.Model.senderId);
 0317    }
 318
 319    protected virtual void OnMessageTriggerHover(DefaultChatEntry chatEntry)
 320    {
 0321        if (contextMenu == null || contextMenu.isVisible)
 0322            return;
 323
 0324        messageHoverText.text = chatEntry.messageLocalDateTime;
 0325        chatEntry.DockHoverPanel((RectTransform) messageHoverPanel.transform);
 0326        messageHoverPanel.SetActive(true);
 0327    }
 328
 329    private void OnMessageCoordinatesTriggerHover(DefaultChatEntry chatEntry, ParcelCoordinates parcelCoordinates)
 330    {
 0331        messageHoverGotoText.text = $"{parcelCoordinates} INFO";
 0332        chatEntry.DockHoverPanel((RectTransform) messageHoverGotoPanel.transform);
 0333        messageHoverGotoPanel.SetActive(true);
 0334    }
 335
 336    private void OnMessageCancelGotoHover()
 337    {
 0338        messageHoverGotoPanel.SetActive(false);
 0339        messageHoverGotoText.text = string.Empty;
 0340    }
 341
 0342    private void SortEntries() => isSortingDirty = true;
 343
 344    private void SortEntriesImmediate()
 345    {
 0346        if (this.entries.Count <= 0) return;
 347
 0348        var entries = this.entries.Values.OrderBy(x => x.Model.timestamp).ToList();
 349
 0350        for (var i = 0; i < entries.Count; i++)
 351        {
 0352            if (entries[i].transform.GetSiblingIndex() != i)
 0353                entries[i].transform.SetSiblingIndex(i);
 354        }
 0355    }
 356
 0357    private void HandleNextChatInHistoryInput(DCLAction_Trigger action) => OnNextChatInHistory?.Invoke();
 358
 0359    private void HandlePreviousChatInHistoryInput(DCLAction_Trigger action) => OnPreviousChatInHistory?.Invoke();
 360
 361    private void UpdateLayout()
 362    {
 363        // we have to wait several frames before updating the layout
 364        // every message entry waits for 3 frames before updating its own layout
 365        // we gotta force the layout updating after that
 366        // TODO: simplify this change to a bool when we update to a working TextMeshPro version
 0367        updateLayoutDelayedFrames = 4;
 0368    }
 369
 370    private ChatEntry GetFirstEntry()
 371    {
 0372        ChatEntry firstEntry = null;
 373
 0374        for (var i = 0; i < chatEntriesContainer.childCount; i++)
 375        {
 0376            var firstChildTransform = chatEntriesContainer.GetChild(i);
 0377            var entry = firstChildTransform.GetComponent<ChatEntry>();
 0378            if (entry == null) continue;
 0379            firstEntry = entry;
 0380            break;
 381        }
 382
 0383        return firstEntry;
 384    }
 385
 386    [Serializable]
 387    private struct Model
 388    {
 389        public bool isInputFieldFocused;
 390        public string inputFieldText;
 391        public bool enableFadeoutMode;
 392        public ChatEntryModel[] entries;
 393    }
 394}