< Summary

Class:DCL.Huds.QuestsTracker.QuestsTrackerEntry
Assembly:QuestsTrackerHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/QuestsTrackerHUD/QuestsTrackerEntry.cs
Covered lines:78
Uncovered lines:79
Coverable lines:157
Total lines:335
Line coverage:49.6% (78 of 157)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
QuestsTrackerEntry()0%110100%
QuestsTrackerEntry()0%110100%
Awake()0%110100%
OutDelayRoutine()0%5.673033.33%
Populate(...)0%13.0213094.87%
CreateSection()0%110100%
DestroySectionEntry(...)0%6200%
SetIcon(...)0%5.323036.36%
Sequence()0%277.111703.45%
StopSequence()0%2.062075%
ClearSectionRoutines()0%12.415033.33%
SetExpandCollapseState(...)0%330100%
OnPinToggleValueChanged(...)0%30500%
SetPinStatus(...)0%110100%
AddRewardToGive(...)0%2100%
StartDestroy()0%110100%
DestroySequence()0%64050%
ProgressSequence()0%20400%
WaitForTaskRoutines()0%20400%
OnDestroy()0%2.52050%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/QuestsTrackerHUD/QuestsTrackerEntry.cs

#LineLine coverage
 1using DCL.Helpers;
 2using System;
 3using System.Collections;
 4using System.Collections.Generic;
 5using System.Linq;
 6using TMPro;
 7using UnityEngine;
 8using UnityEngine.UI;
 9
 10namespace DCL.Huds.QuestsTracker
 11{
 12    public class QuestsTrackerEntry : MonoBehaviour
 13    {
 14        internal const float OUT_ANIM_DELAY = 0.5f;
 15        private const float DELAY_TO_DESTROY = 0.5f;
 116        private static readonly int OUT_ANIM_TRIGGER = Animator.StringToHash("Out");
 17        public event Action OnLayoutRebuildRequested;
 18        public event Action<QuestModel> OnQuestCompleted;
 19        public event Action<QuestReward> OnRewardObtained;
 20
 21        [SerializeField] internal TextMeshProUGUI questTitle;
 22        [SerializeField] internal TextMeshProUGUI questProgressText;
 23        [SerializeField] internal Image progress;
 24        [SerializeField] internal RectTransform sectionContainer;
 25        [SerializeField] internal GameObject sectionPrefab;
 26        [SerializeField] internal Button expandCollapseButton;
 27        [SerializeField] internal GameObject expandIcon;
 28        [SerializeField] internal GameObject collapseIcon;
 29        [SerializeField] internal Toggle pinQuestToggle;
 30        [SerializeField] internal RawImage iconImage;
 31        [SerializeField] internal Animator containerAnimator;
 32
 033        public bool isReadyForDisposal { get; private set; } = false;
 034        private static BaseCollection<string> pinnedQuests => DataStore.i.Quests.pinnedQuests;
 035        private bool isProgressAnimationDone => Math.Abs(progress.fillAmount - progressTarget) < Mathf.Epsilon;
 36
 37        private float progressTarget = 0;
 38        private AssetPromise_Texture iconPromise;
 39
 40        internal QuestModel quest;
 41        internal bool isExpanded;
 42        internal bool isPinned;
 43        internal bool outAnimDone = false;
 44        internal bool hasProgressedThisUpdate = false;
 45
 2046        internal readonly Dictionary<string, QuestsTrackerSection> sectionEntries = new Dictionary<string, QuestsTracker
 2047        private readonly List<QuestReward> rewardsToNotify = new List<QuestReward>();
 2048        private readonly List<Coroutine> sectionRoutines = new List<Coroutine>();
 49        private Coroutine sequenceRoutine;
 50        private Coroutine progressRoutine;
 51
 52        public void Awake()
 53        {
 1854            pinQuestToggle.onValueChanged.AddListener(OnPinToggleValueChanged);
 55
 1856            expandCollapseButton.gameObject.SetActive(false);
 1857            SetExpandCollapseState(true);
 1858            expandCollapseButton.onClick.AddListener(() => SetExpandCollapseState(!isExpanded));
 1859            StartCoroutine(OutDelayRoutine());
 60
 1861            AudioScriptableObjects.fadeIn.Play();
 1862        }
 63
 64        private IEnumerator OutDelayRoutine()
 65        {
 1866            yield return new WaitForSeconds(OUT_ANIM_DELAY);
 067            outAnimDone = true;
 068        }
 69
 70        public void Populate(QuestModel newQuest)
 71        {
 1672            StopSequence();
 73
 1674            quest = newQuest;
 1675            SetIcon(quest.thumbnail_entry);
 3576            QuestTask[] allTasks = quest.sections.SelectMany(x => x.tasks).ToArray();
 77
 3578            int completedTasksAmount = allTasks.Count(x => x.progress >= 1);
 1679            questTitle.text = $"{quest.name}";
 1680            questProgressText.text = $"{completedTasksAmount}/{allTasks.Length}";
 1681            progress.fillAmount = quest.oldProgress;
 1682            progressTarget = quest.progress;
 83
 5484            hasProgressedThisUpdate = newQuest.sections.Any(x => x.tasks.Any(y => y.justProgressed));
 85
 1686            List<string> entriesToRemove = sectionEntries.Keys.ToList();
 1687            List<QuestsTrackerSection> visibleSectionEntries = new List<QuestsTrackerSection>();
 1688            List<QuestsTrackerSection> newSectionEntries = new List<QuestsTrackerSection>();
 7089            for (var i = 0; i < quest.sections.Length; i++)
 90            {
 1991                QuestSection section = quest.sections[i];
 92
 3893                bool hasTasks = section.tasks.Any(x => x.status != QuestsLiterals.Status.BLOCKED && (x.progress < 1 || x
 1994                if (!hasTasks)
 95                    continue;
 96
 3697                bool isVisible = section.tasks.Any(x => x.status != QuestsLiterals.Status.BLOCKED && ((x.progress < 1 &&
 98
 1899                entriesToRemove.Remove(section.id);
 18100                if (!sectionEntries.TryGetValue(section.id, out QuestsTrackerSection sectionEntry))
 101                {
 18102                    sectionEntry = CreateSection();
 103                    //New tasks are invisible
 18104                    sectionEntries.Add(section.id, sectionEntry);
 105                }
 106
 18107                sectionEntry.gameObject.SetActive(isVisible);
 18108                sectionEntry.Populate(section);
 18109                sectionEntry.transform.SetAsLastSibling();
 110
 18111                if (sectionEntry.gameObject.activeSelf)
 17112                    visibleSectionEntries.Add(sectionEntry);
 113                else
 1114                    newSectionEntries.Add(sectionEntry);
 115            }
 116
 32117            for (int index = 0; index < entriesToRemove.Count; index++)
 118            {
 0119                DestroySectionEntry(entriesToRemove[index]);
 120            }
 121
 16122            expandCollapseButton.gameObject.SetActive(sectionEntries.Count > 0);
 16123            SetExpandCollapseState(true);
 16124            OnLayoutRebuildRequested?.Invoke();
 125
 16126            sequenceRoutine = StartCoroutine(Sequence(visibleSectionEntries, newSectionEntries));
 16127        }
 128
 129        private QuestsTrackerSection CreateSection()
 130        {
 18131            var sectionEntry = Instantiate(sectionPrefab, sectionContainer).GetComponent<QuestsTrackerSection>();
 54132            sectionEntry.OnLayoutRebuildRequested += () => OnLayoutRebuildRequested?.Invoke();
 18133            sectionEntry.OnDestroyed += (sectionId) => sectionEntries.Remove(sectionId);
 18134            return sectionEntry;
 135        }
 136
 137        private void DestroySectionEntry(string taskId)
 138        {
 0139            if (!sectionEntries.TryGetValue(taskId, out QuestsTrackerSection sectionEntry))
 0140                return;
 0141            Destroy(sectionEntry.gameObject);
 0142            sectionEntries.Remove(taskId);
 0143        }
 144
 145        internal void SetIcon(string iconURL)
 146        {
 16147            if (iconPromise != null)
 148            {
 0149                iconPromise.ClearEvents();
 0150                AssetPromiseKeeper_Texture.i.Forget(iconPromise);
 151            }
 152
 16153            if (string.IsNullOrEmpty(iconURL))
 154            {
 16155                iconImage.gameObject.SetActive(false);
 16156                return;
 157            }
 158
 0159            iconPromise = new AssetPromise_Texture(iconURL);
 0160            iconPromise.OnSuccessEvent += assetTexture =>
 161            {
 0162                iconImage.gameObject.SetActive(true);
 0163                iconImage.texture = assetTexture.texture;
 0164            };
 0165            iconPromise.OnFailEvent += assetTexture =>
 166            {
 0167                iconImage.gameObject.SetActive(false);
 0168                Debug.Log($"Error downloading quest tracker entry icon: {iconURL}");
 0169            };
 170
 0171            AssetPromiseKeeper_Texture.i.Keep(iconPromise);
 0172        }
 173
 174        private IEnumerator Sequence(List<QuestsTrackerSection> visibleSections, List<QuestsTrackerSection> newSections)
 175        {
 36176            yield return new WaitUntil(() => outAnimDone);
 177
 0178            ClearSectionRoutines();
 179
 0180            if (progressRoutine != null)
 0181                StopCoroutine(progressRoutine);
 0182            progressRoutine = StartCoroutine(ProgressSequence());
 183
 184            //Progress of currently visible sections
 0185            for (int i = 0; i < visibleSections.Count; i++)
 186            {
 0187                sectionRoutines.Add(StartCoroutine(visibleSections[i].Sequence()));
 188            }
 189
 0190            yield return WaitForTaskRoutines();
 191
 192            //Show and progress of new tasks
 0193            for (int i = 0; i < newSections.Count; i++)
 194            {
 0195                newSections[i].gameObject.SetActive(true);
 0196                sectionRoutines.Add(StartCoroutine(newSections[i].Sequence()));
 197            }
 198
 0199            OnLayoutRebuildRequested?.Invoke();
 0200            yield return WaitForTaskRoutines();
 201
 0202            OnLayoutRebuildRequested?.Invoke();
 203            //The entry should exit automatically if questCompleted or no progress, therefore the use of MinValue
 0204            DateTime tasksIdleTime = (quest.isCompleted || !hasProgressedThisUpdate) ? DateTime.MinValue : DateTime.Now;
 0205            yield return new WaitUntil(() => isProgressAnimationDone && !isPinned && (DateTime.Now - tasksIdleTime) > Ti
 206
 0207            if (quest.isCompleted)
 0208                OnQuestCompleted?.Invoke(quest);
 209
 0210            for (int i = 0; i < rewardsToNotify.Count; i++)
 211            {
 0212                OnRewardObtained?.Invoke(rewardsToNotify[i]);
 213            }
 214
 0215            rewardsToNotify.Clear();
 216
 0217            isReadyForDisposal = true;
 0218        }
 219
 220        private void StopSequence()
 221        {
 16222            if (sequenceRoutine != null)
 0223                StopCoroutine(sequenceRoutine);
 16224            ClearSectionRoutines();
 16225        }
 226
 227        private void ClearSectionRoutines()
 228        {
 16229            if (sectionRoutines.Count > 0)
 230            {
 0231                for (int i = 0; i < sectionRoutines.Count; i++)
 232                {
 0233                    if (sectionRoutines[i] != null)
 0234                        StopCoroutine(sectionRoutines[i]);
 235                }
 236
 0237                sectionRoutines.Clear();
 238            }
 239
 32240            foreach (var section in sectionEntries)
 241            {
 0242                section.Value.ClearTaskRoutines();
 243            }
 16244        }
 245
 246        internal void SetExpandCollapseState(bool newIsExpanded)
 247        {
 34248            isExpanded = newIsExpanded;
 34249            expandIcon.SetActive(!isExpanded);
 34250            collapseIcon.SetActive(isExpanded);
 34251            sectionContainer.gameObject.SetActive(isExpanded);
 252
 104253            foreach (QuestsTrackerSection section in sectionEntries.Values)
 254            {
 18255                section.SetExpandCollapseState(newIsExpanded);
 256            }
 257
 34258            OnLayoutRebuildRequested?.Invoke();
 10259        }
 260
 261        private void OnPinToggleValueChanged(bool isOn)
 262        {
 0263            if (quest == null)
 0264                return;
 265
 0266            if (!quest.canBePinned)
 267            {
 0268                pinnedQuests.Remove(quest.id);
 0269                SetPinStatus(false);
 0270                return;
 271            }
 272
 0273            if (isOn)
 274            {
 0275                if (!pinnedQuests.Contains(quest.id))
 0276                    pinnedQuests.Add(quest.id);
 0277            }
 278            else
 279            {
 0280                pinnedQuests.Remove(quest.id);
 281            }
 282
 0283            QuestsUIAnalytics.SendQuestPinChanged(quest.id, isOn, QuestsUIAnalytics.UIContext.QuestsTracker);
 0284        }
 285
 286        public void SetPinStatus(bool newIsPinned)
 287        {
 14288            isPinned = newIsPinned;
 14289            pinQuestToggle.SetIsOnWithoutNotify(newIsPinned);
 14290        }
 291
 0292        public void AddRewardToGive(QuestReward reward) { rewardsToNotify.Add(reward); }
 293
 2294        public void StartDestroy() { StartCoroutine(DestroySequence()); }
 295
 296        private IEnumerator DestroySequence()
 297        {
 1298            AudioScriptableObjects.fadeOut.Play();
 1299            containerAnimator.SetTrigger(OUT_ANIM_TRIGGER);
 1300            yield return WaitForSecondsCache.Get(DELAY_TO_DESTROY);
 301
 0302            OnLayoutRebuildRequested?.Invoke();
 0303            Destroy(gameObject);
 0304        }
 305
 306        private IEnumerator ProgressSequence()
 307        {
 0308            while (Math.Abs(progress.fillAmount - progressTarget) > Mathf.Epsilon)
 309            {
 0310                progress.fillAmount = Mathf.MoveTowards(progress.fillAmount, progressTarget, Time.deltaTime);
 0311                yield return null;
 312            }
 313
 0314            progressRoutine = null;
 0315        }
 316
 317        private IEnumerator WaitForTaskRoutines()
 318        {
 0319            for (int i = 0; i < sectionRoutines.Count; i++)
 320            {
 321                //yielding Coroutines (not IEnumerators) allows us to wait for them in parallel
 0322                yield return sectionRoutines[i];
 323            }
 0324        }
 325
 326        private void OnDestroy()
 327        {
 18328            if (iconPromise != null)
 329            {
 0330                iconPromise.ClearEvents();
 0331                AssetPromiseKeeper_Texture.i.Forget(iconPromise);
 332            }
 18333        }
 334    }
 335}