< Summary

Class:DCL.Chat.HUD.DefaultChatEntry
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/DefaultChatEntry.cs
Covered lines:66
Uncovered lines:68
Coverable lines:134
Total lines:320
Line coverage:49.2% (66 of 134)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DefaultChatEntry()0%110100%
Populate(...)0%110100%
PopulateTask()0%990100%
GetUserString(...)0%9.029093.75%
GetCoordinatesLink(...)0%330100%
PlaySfx(...)0%76.9910012.5%
IsRecentMessage(...)0%6200%
PreloadSceneMetadata(...)0%220100%
OnPointerClick(...)0%42600%
OnPointerEnter(...)0%6200%
OnPointerExit(...)0%17.85020%
OnDisable()0%110100%
OnDestroy()0%110100%
SetFadeout(...)0%6200%
DockContextMenu(...)0%2100%
DockHoverPanel(...)0%2100%
Update()0%110100%
CheckHoverCoordinates()0%13.35030.77%
ProcessHoverPanelTimer()0%14.115028.57%
ProcessHoverGotoPanelTimer()0%14.115028.57%
RemoveTabs(...)0%2.152066.67%

File(s)

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

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using DCL.Helpers;
 3using DCL.Interface;
 4using DCL.SettingsCommon;
 5using System;
 6using System.Threading;
 7using TMPro;
 8using UnityEngine;
 9using UnityEngine.EventSystems;
 10
 11namespace DCL.Chat.HUD
 12{
 13    public class DefaultChatEntry : ChatEntry, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
 14    {
 15        [SerializeField] internal TextMeshProUGUI body;
 3316        [SerializeField] internal float timeToHoverPanel = 1f;
 3317        [SerializeField] internal float timeToHoverGotoPanel = 1f;
 3318        [SerializeField] internal bool showUserName = true;
 19        [SerializeField] private RectTransform hoverPanelPositionReference;
 20        [SerializeField] private RectTransform contextMenuPositionReference;
 21
 22        private float hoverPanelTimer;
 23        private float hoverGotoPanelTimer;
 24        private bool isOverCoordinates;
 25        private bool isShowingPreview;
 26        private ParcelCoordinates currentCoordinates;
 27        private ChatEntryModel model;
 28
 3329        private readonly CancellationTokenSource populationTaskCancellationTokenSource = new CancellationTokenSource();
 30
 031        public override ChatEntryModel Model => model;
 32
 33        public override string DateString =>
 034            DateTimeOffset.FromUnixTimeMilliseconds((long) Model.timestamp)
 35                .ToLocalTime()
 36                .ToString("MM/dd/yyyy h:mm:ss tt");
 37        public override event Action<ChatEntry> OnUserNameClicked;
 38        public override event Action<ChatEntry> OnTriggerHover;
 39        public override event Action<ChatEntry, ParcelCoordinates> OnTriggerHoverGoto;
 40        public override event Action OnCancelHover;
 41        public override event Action OnCancelGotoHover;
 42
 43        public override void Populate(ChatEntryModel chatEntryModel) =>
 1244            PopulateTask(chatEntryModel, populationTaskCancellationTokenSource.Token).Forget();
 45
 46        private async UniTask PopulateTask(ChatEntryModel chatEntryModel, CancellationToken cancellationToken)
 47        {
 1248            model = chatEntryModel;
 49
 1250            chatEntryModel.bodyText = body.ReplaceUnsupportedCharacters(chatEntryModel.bodyText, '?');
 1251            chatEntryModel.bodyText = RemoveTabs(chatEntryModel.bodyText);
 1252            var userString = GetUserString(chatEntryModel);
 53
 54            // Due to a TMPro bug in Unity 2020 LTS we have to wait several frames before setting the body.text to avoid
 55            // client crash. More info at https://github.com/decentraland/unity-renderer/pull/2345#issuecomment-11557535
 56            // TODO: Remove hack in a newer Unity/TMPro version
 3657            await UniTask.NextFrame(cancellationToken);
 3658            await UniTask.NextFrame(cancellationToken);
 3659            await UniTask.NextFrame(cancellationToken);
 60
 1261            if (!string.IsNullOrEmpty(userString) && showUserName)
 962                body.text = $"{userString} {chatEntryModel.bodyText}";
 63            else
 364                body.text = chatEntryModel.bodyText;
 65
 1266            body.text = GetCoordinatesLink(body.text);
 67
 1268            (transform as RectTransform).ForceUpdateLayout();
 69
 1270            PlaySfx(chatEntryModel);
 1271        }
 72
 73        private string GetUserString(ChatEntryModel chatEntryModel)
 74        {
 1275            if (string.IsNullOrEmpty(model.senderName)) return "";
 76
 1277            var baseName = model.senderName;
 78
 1279            switch (chatEntryModel.subType)
 80            {
 81                case ChatEntryModel.SubType.SENT:
 782                    switch (chatEntryModel.messageType)
 83                    {
 84                        case ChatMessage.Type.PUBLIC:
 685                        case ChatMessage.Type.PRIVATE when chatEntryModel.isChannelMessage:
 186                            baseName = "You";
 187                            break;
 88                        case ChatMessage.Type.PRIVATE:
 689                            baseName = $"To {chatEntryModel.recipientName}";
 690                            break;
 91                    }
 92
 93                    break;
 94                case ChatEntryModel.SubType.RECEIVED:
 595                    switch (chatEntryModel.messageType)
 96                    {
 97                        case ChatMessage.Type.PRIVATE:
 398                            baseName = $"<color=#5EBD3D>From <link=username://{baseName}>{baseName}</link></color>";
 399                            break;
 100                        case ChatMessage.Type.PUBLIC:
 1101                            baseName = $"<link=username://{baseName}>{baseName}</link>";
 102                            break;
 103                    }
 104                    break;
 105            }
 106
 12107            baseName = $"<b>{baseName}:</b>";
 108
 12109            return baseName;
 110        }
 111
 112        private string GetCoordinatesLink(string body)
 113        {
 12114            if (!CoordinateUtils.HasValidTextCoordinates(body))
 9115                return body;
 3116            var textCoordinates = CoordinateUtils.GetTextCoordinates(body);
 117
 16118            for (var i = 0; i < textCoordinates.Count; i++)
 119            {
 120                // TODO: the preload should not be here
 5121                PreloadSceneMetadata(CoordinateUtils.ParseCoordinatesString(textCoordinates[i]));
 122
 5123                body = body.Replace(textCoordinates[i],
 124                    $"</noparse><link={textCoordinates[i]}><color=#4886E3><u>{textCoordinates[i]}</u></color></link><nop
 125            }
 126
 3127            return body;
 128        }
 129
 130        private void PlaySfx(ChatEntryModel chatEntryModel)
 131        {
 12132            if (HUDAudioHandler.i == null)
 12133                return;
 134
 0135            if (IsRecentMessage(chatEntryModel) && Settings.i.audioSettings.Data.chatSFXEnabled)
 136            {
 0137                switch (chatEntryModel.messageType)
 138                {
 139                    case ChatMessage.Type.PUBLIC:
 140                        // Check whether or not the message was sent by the local player
 0141                        if (chatEntryModel.senderId == UserProfile.GetOwnUserProfile().userId)
 0142                            AudioScriptableObjects.chatSend.Play(true);
 143                        else
 0144                            AudioScriptableObjects.chatReceiveGlobal.Play(true);
 0145                        break;
 146                    case ChatMessage.Type.PRIVATE:
 0147                        switch (chatEntryModel.subType)
 148                        {
 149                            case ChatEntryModel.SubType.RECEIVED:
 0150                                AudioScriptableObjects.chatReceivePrivate.Play(true);
 0151                                break;
 152                            case ChatEntryModel.SubType.SENT:
 0153                                AudioScriptableObjects.chatSend.Play(true);
 0154                                break;
 155                        }
 156
 157                        break;
 158                    case ChatMessage.Type.SYSTEM:
 0159                        AudioScriptableObjects.chatReceiveGlobal.Play(true);
 160                        break;
 161                }
 162            }
 163
 0164            HUDAudioHandler.i.RefreshChatLastCheckedTimestamp();
 0165        }
 166
 167        private bool IsRecentMessage(ChatEntryModel chatEntryModel)
 168        {
 0169            return chatEntryModel.timestamp > HUDAudioHandler.i.chatLastCheckedTimestamp
 170                   && (DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeMilliseconds((long) chatEntryModel.timestamp))
 171                   .TotalSeconds < 30;
 172        }
 173
 174        private void PreloadSceneMetadata(ParcelCoordinates parcelCoordinates)
 175        {
 5176            if (MinimapMetadata.GetMetadata().GetSceneInfo(parcelCoordinates.x, parcelCoordinates.y) == null)
 5177                WebInterface.RequestScenesInfoAroundParcel(new Vector2(parcelCoordinates.x, parcelCoordinates.y), 2);
 5178        }
 179
 180        public void OnPointerClick(PointerEventData pointerEventData)
 181        {
 0182            if (pointerEventData.button == PointerEventData.InputButton.Left)
 183            {
 0184                var linkIndex =
 185                    TMP_TextUtilities.FindIntersectingLink(body, pointerEventData.position, body.canvas.worldCamera);
 0186                if (linkIndex == -1) return;
 187
 0188                var link = body.textInfo.linkInfo[linkIndex].GetLinkID();
 189
 0190                if (CoordinateUtils.HasValidTextCoordinates(link))
 191                {
 0192                    DataStore.i.HUDs.gotoPanelVisible.Set(true);
 0193                    var parcelCoordinate = CoordinateUtils.ParseCoordinatesString(link);
 0194                    DataStore.i.HUDs.gotoPanelCoordinates.Set(parcelCoordinate);
 195                }
 0196                else if (link.StartsWith("username://"))
 0197                    OnUserNameClicked?.Invoke(this);
 198            }
 0199        }
 200
 201        public void OnPointerEnter(PointerEventData pointerEventData)
 202        {
 0203            if (pointerEventData == null)
 0204                return;
 205
 0206            hoverPanelTimer = timeToHoverPanel;
 0207        }
 208
 209        public void OnPointerExit(PointerEventData pointerEventData)
 210        {
 12211            if (pointerEventData == null)
 12212                return;
 213
 0214            hoverPanelTimer = 0f;
 0215            var linkIndex =
 216                TMP_TextUtilities.FindIntersectingLink(body, pointerEventData.position,
 217                    DataStore.i.camera.hudsCamera.Get());
 0218            if (linkIndex == -1)
 219            {
 0220                isOverCoordinates = false;
 0221                hoverGotoPanelTimer = 0;
 0222                OnCancelGotoHover?.Invoke();
 223            }
 224
 0225            OnCancelHover?.Invoke();
 0226        }
 227
 228        private void OnDisable()
 229        {
 12230            OnPointerExit(null);
 12231        }
 232
 233        private void OnDestroy()
 234        {
 12235            populationTaskCancellationTokenSource.Cancel();
 12236        }
 237
 238        public override void SetFadeout(bool enabled)
 239        {
 0240            if (enabled) return;
 0241            group.alpha = 1;
 0242        }
 243
 244        public override void DockContextMenu(RectTransform panel)
 245        {
 0246            panel.pivot = new Vector2(0, 0);
 0247            panel.position = contextMenuPositionReference.position;
 0248        }
 249
 250        public override void DockHoverPanel(RectTransform panel)
 251        {
 0252            panel.pivot = hoverPanelPositionReference.pivot;
 0253            panel.position = hoverPanelPositionReference.position;
 0254        }
 255
 256        private void Update()
 257        {
 258            // TODO: why it needs to be in an update? what about OnPointerEnter/OnPointerExit?
 48259            CheckHoverCoordinates();
 48260            ProcessHoverPanelTimer();
 48261            ProcessHoverGotoPanelTimer();
 48262        }
 263
 264        private void CheckHoverCoordinates()
 265        {
 48266            if (isOverCoordinates)
 0267                return;
 268
 48269            var linkIndex =
 270                TMP_TextUtilities.FindIntersectingLink(body, Input.mousePosition, DataStore.i.camera.hudsCamera.Get());
 271
 48272            if (linkIndex == -1)
 48273                return;
 274
 0275            var link = body.textInfo.linkInfo[linkIndex].GetLinkID();
 0276            if (!CoordinateUtils.HasValidTextCoordinates(link)) return;
 277
 0278            isOverCoordinates = true;
 0279            currentCoordinates = CoordinateUtils.ParseCoordinatesString(link);
 0280            hoverGotoPanelTimer = timeToHoverGotoPanel;
 0281            OnCancelHover?.Invoke();
 0282        }
 283
 284        private void ProcessHoverPanelTimer()
 285        {
 48286            if (hoverPanelTimer <= 0f || isOverCoordinates)
 48287                return;
 288
 0289            hoverPanelTimer -= Time.deltaTime;
 0290            if (hoverPanelTimer <= 0f)
 291            {
 0292                hoverPanelTimer = 0f;
 0293                OnTriggerHover?.Invoke(this);
 294            }
 0295        }
 296
 297        private void ProcessHoverGotoPanelTimer()
 298        {
 48299            if (hoverGotoPanelTimer <= 0f || !isOverCoordinates)
 48300                return;
 301
 0302            hoverGotoPanelTimer -= Time.deltaTime;
 0303            if (hoverGotoPanelTimer <= 0f)
 304            {
 0305                hoverGotoPanelTimer = 0f;
 0306                OnTriggerHoverGoto?.Invoke(this, currentCoordinates);
 307            }
 0308        }
 309
 310        private string RemoveTabs(string text)
 311        {
 12312            if (string.IsNullOrEmpty(text))
 0313                return "";
 314
 315            //NOTE(Brian): ContentSizeFitter doesn't fare well with tabs, so i'm replacing these
 316            //             with spaces.
 12317            return text.Replace("\t", "    ");
 318        }
 319    }
 320}