using System; using System.Collections.Generic; using System.Linq; using Logic.CrashSight; using Logic.Multilingual; using TH1_Core.Events; using TH1_Logic.Oss; using TH1_Logic.Questionnaire; using TH1Resource; using TMPro; using UnityEngine; using UnityEngine.UI; #if STEAM_CHANNEL || STEAMWORKS_NET using Steamworks; #endif namespace TH1_UI.View.Outside { public class UIOutsideQuestionnaireView : Base.View { private const string DataAssetPath = "Export/QuestionnaireDataAssets"; private const string QuestionPrefabPath = "Prefab/UI/Outside/UIOutsideQuestionnaireQuestion"; private const string OptionPrefabPath = "Prefab/UI/Outside/UIOutsideQuestionnaireOption"; private const uint BodyFontGroupId = 1; private const uint TitleFontGroupId = 2; private const uint TextQuestionnaireCenter = 30978; private const uint TextCurrentQuestionnaire = 30979; private const uint TextLatestSubmission = 30984; private const uint TextReturn = 1048; private const uint TextView = 19671; private const uint TextNoAvailableQuestionnaires = 31171; private const uint TextNoCurrentQuestionnaire = 31172; private const uint TextLongTermQuestionnaire = 31173; private const uint TextNoLongTermQuestionnaires = 31174; private const uint TextPastQuestionnaires = 31175; private const uint TextNoPastQuestionnaires = 31176; private const uint TextNoQuestionnairesToDisplay = 31177; private const uint TextPastQuestionnairesCollapsed = 31178; private const uint TextViewRecordsCount = 31179; private const uint TextFillQuestionnaire = 31180; private const uint TextFormSubmitHint = 31181; private const uint TextClear = 31182; private const uint TextResponseRecords = 31183; private const uint TextNoResponseRecords = 31184; private const uint TextReadOnlyRecord = 31185; private const uint TextBackToRecords = 31186; private const uint TextRecordIndex = 31187; private const uint TextQuestionnaireMissing = 31188; private const uint TextQuestionnaireEnded = 31189; private const uint TextQuestionnaireUnavailable = 31190; private const uint TextQuestionnaireAlreadySubmitted = 31191; private const uint TextQuestionnaireSubmissionLimit = 31192; private const uint TextQuestionnaireInterval = 31193; private const uint TextListMetaNotFilled = 31194; private const uint TextListMetaSubmitted = 31195; private const uint TextStatusCurrent = 30981; private const uint TextStatusLongTerm = 31198; private const uint TextStatusPast = 31199; private const uint TextStatusHidden = 31200; private const uint TextUnknownTime = 31201; private const uint TextUploaded = 31202; private const uint TextLocalOnly = 31203; private const uint TextWaitingUploadResult = 31204; public static readonly Color InkColor = new Color(0.25490198f, 0.23137257f, 0.41960788f, 1f); public static readonly Color InkMutedColor = new Color(0.53f, 0.53f, 0.53f, 1f); public static readonly Color InkLightColor = new Color(0.96f, 0.96f, 0.92f, 1f); public static readonly Color GoldColor = new Color(0.91372555f, 0.7176471f, 0.3647059f, 1f); public static readonly Color PanelTint = Color.white; public static readonly Color TransparentPanelTint = new Color(1f, 1f, 1f, 0f); public const float ItemButtonPixelsPerUnitMultiplier = 1f; private const float SidebarWidth = 440f; private const float LibraryFrameHorizontalPadding = 86f; private const float LibraryFrameTopPadding = 42f; private const float LibraryFrameBottomPadding = 58f; private const float LibraryTopBarHeight = 203.6337f; private const float TitleAreaHeight = 82f; private const float ContentAreaSpacing = 18f; private const int DetailPanelHorizontalPadding = 42; private const int DetailPanelTopPadding = 34; private const int DetailPanelBottomPadding = 30; private const float DetailPanelSpacing = 22f; private const float DetailContentSpacing = 18f; private const float DetailHeaderSpacing = 6f; private const float PrimaryButtonPixelsPerUnitMultiplier = 1f; private const float SecondaryButtonPixelsPerUnitMultiplier = 1f; private const string SpriteFullscreenBackdrop = "TH1UI/Common/CommonBG/CommonVerticleStripeTileBG"; private const string SpriteLibraryTopBar = "TH1UI/TechTree/TechTreeTopBar"; private const string SpriteCommonWindow = "TH1UI/Common/CommonBG/CommonWindowBG"; private const string SpriteCommonPanel = "ArtResources/TH1UI/Common/CommonPanelBG"; private const string SpriteCommonLabel = "TH1UI/Common/CommonBG/CommonLabelBG"; private const string SpriteCommonSelectLabel = "TH1UI/Common/CommonBG/CommonSelectLabelBG"; private const string SpriteButtonPrimary = "TH1UI/Common/CommonButton/CheckButton1"; private const string SpriteButtonSecondary = "TH1UI/Common/CommonButton/CheckButton2"; private const string SpriteButtonDisabled = "TH1UI/Common/CommonButton/CheckButton_Gray"; private const string SpriteReturnButton = "TH1UI/Common/CommonButton/CommonReturnButton"; private const string SpriteItemButton = "TH1UI/Common/CommonButton/ItemButtonBG"; private const string SpriteItemButtonSelected = "TH1UI/Common/CommonButton/ItemButtonSelectedBG"; private const string SpriteDivider = "TH1UI/Common/CommonDeco/DecoDashedHoriLine"; public Button CloseButton; public Button SubmitButton; public Button RefillButton; public TextMeshProUGUI TitleText; public TextMeshProUGUI DescriptionText; public TextMeshProUGUI StatusText; public TextMeshProUGUI SubmitButtonText; public TextMeshProUGUI RefillButtonText; public TextMeshProUGUI CloseButtonText; public Transform QuestionListContent; public GameObject QuestionPrefab; public GameObject OptionPrefab; public GameObject QuestionnaireItemTemplate; public GameObject SectionHeaderTemplate; public GameObject DividerTemplate; public GameObject InfoStripTemplate; public GameObject LatestRecordPreviewTemplate; public GameObject RecordRowTemplate; public GameObject PrimaryButtonTemplate; public GameObject SecondaryButtonTemplate; public ViDelegateAssisstant.Dele OnBtnCloseClick; private readonly List _questionItems = new List(); private readonly List _listItems = new List(); private QuestionnaireDataAssets _dataAssets; private QuestionnaireInfo _questionnaireInfo; private QuestionnaireAnswerSheet _currentSheet; private bool _isUploading; private bool _layoutBuilt; private bool _showExpired; private bool _templatesExtracted; private Transform _runtimeTemplateRoot; private Transform _sidebarContent; private Transform _rightBody; private Transform _rightFooter; private TextMeshProUGUI _expiredToggleText; private Transform _rightHeaderRoot; private TextMeshProUGUI _rightHeaderTitle; private TextMeshProUGUI _rightHeaderSubtitle; private Button _recordsButton; private Button _startButton; private TextMeshProUGUI _recordsButtonText; private TextMeshProUGUI _startButtonText; private static readonly Dictionary SpriteCache = new Dictionary(); protected override void OnInit() { base.OnInit(); EnsureLayout(); } #if UNITY_EDITOR [ContextMenu("Questionnaire/Rebuild Design Templates")] public void RebuildDesignTemplatesContext() { if (!CanEditDesignTemplateHierarchy()) { Debug.LogWarning( "[UIOutsideQuestionnaireView] Open the questionnaire prefab in Prefab Mode before rebuilding design templates."); return; } EnsureDesignTemplates(true); UnityEditor.EditorUtility.SetDirty(this); } private bool CanEditDesignTemplateHierarchy() { if (this == null || gameObject == null) return false; var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); if (prefabStage != null && prefabStage.prefabContentsRoot != null && transform.root == prefabStage.prefabContentsRoot.transform) { return true; } return gameObject.scene.IsValid() && gameObject.scene.isLoaded && !UnityEditor.EditorUtility.IsPersistent(gameObject); } #endif public void SetContent(ShowUIOutsideQuestionnaire evt) { EnsureLayout(); if (Application.isPlaying) { ExtractPrefabTemplates(); } LoadAssetsIfNeeded(); CloseButton.onClick.RemoveAllListeners(); CloseButton.onClick.AddListener(OnCloseClicked); SetUploading(false); var selected = ResolveInitialQuestionnaire(evt); if (selected != null && selected.GetEffectiveStatus(DateTime.UtcNow) == QuestionnaireStatus.Expired) { _showExpired = true; } BuildQuestionnaireList(); if (selected == null) { ShowEmptyState(L(TextNoAvailableQuestionnaires)); return; } SelectQuestionnaire(selected); } public void OnCloseView() { } public static string ResolveText(string raw) { if (string.IsNullOrEmpty(raw)) return string.Empty; if (uint.TryParse(raw, out var id)) { return MultilingualManager.Instance.GetMultilingualText(id); } return raw; } private static string L(uint id) { return MultilingualManager.Instance.GetMultilingualText(id); } private static string L(uint id, params object[] values) { var text = L(id); if (string.IsNullOrEmpty(text) || values == null || values.Length == 0) return text; foreach (var value in values) { var index = text.IndexOf("{param}", StringComparison.Ordinal); if (index < 0) break; text = text.Remove(index, 7).Insert(index, value?.ToString() ?? string.Empty); } return text; } public static TextMeshProUGUI CreateText(Transform parent, string objectName, string text, float fontSize, Color color, TextAlignmentOptions alignment) { var go = new GameObject(objectName, typeof(RectTransform), typeof(TextMeshProUGUI)); go.transform.SetParent(parent, false); var label = go.GetComponent(); label.text = text; label.fontSize = fontSize; label.color = color; label.alignment = alignment; label.enableWordWrapping = false; label.raycastTarget = false; ApplyFontGroup(label, BodyFontGroupId); return label; } public static void ApplyTitleFont(TextMeshProUGUI label) { if (label == null) return; ApplyFontGroup(label, TitleFontGroupId); } private static void ApplyFontGroup(TextMeshProUGUI label, uint fontGroupId) { if (label == null) return; label.fontStyle = FontStyles.Normal; label.fontWeight = FontWeight.Regular; var font = MultilingualManager.Instance.GetMultilingualFont(fontGroupId); if (font != null) label.font = font; } public static Sprite LoadUiSprite(string path) { if (string.IsNullOrEmpty(path)) return null; if (SpriteCache.TryGetValue(path, out var cached)) return cached; #if UNITY_EDITOR if (!Application.isPlaying) { var editorPath = $"Assets/BundleResources/{path}.png"; var editorSprite = UnityEditor.AssetDatabase.LoadAssetAtPath(editorPath); if (editorSprite != null) { SpriteCache[path] = editorSprite; return editorSprite; } } #endif var sprite = ResourceLoader.Load(path); SpriteCache[path] = sprite; return sprite; } public static void ApplySprite(Image image, string path, Color tint, bool preserveAspect = false, float pixelsPerUnitMultiplier = 1f) { if (image == null) return; image.sprite = LoadUiSprite(path); image.color = tint; image.preserveAspect = preserveAspect; image.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; image.type = image.sprite != null && image.sprite.border != Vector4.zero ? Image.Type.Sliced : Image.Type.Simple; } private void LoadAssetsIfNeeded() { if (_dataAssets == null) { _dataAssets = ResourceLoader.Load(DataAssetPath); } if (QuestionPrefab == null) { QuestionPrefab = ResourceLoader.Load(QuestionPrefabPath); } if (OptionPrefab == null) { OptionPrefab = ResourceLoader.Load(OptionPrefabPath); } } private QuestionnaireInfo ResolveInitialQuestionnaire(ShowUIOutsideQuestionnaire evt) { if (_dataAssets == null) { Debug.LogError("[UIOutsideQuestionnaireView] QuestionnaireDataAssets is missing."); return null; } QuestionnaireInfo selected = null; if (!string.IsNullOrEmpty(evt.QuestionnaireId)) { _dataAssets.GetQuestionnaireInfo(evt.QuestionnaireId, out selected); } selected ??= _dataAssets.GetDefaultQuestionnaire(); if (selected != null && selected.IsVisibleAt(DateTime.UtcNow)) return selected; return GetVisibleQuestionnaires().FirstOrDefault(); } private List GetVisibleQuestionnaires() { return _dataAssets != null ? _dataAssets.GetVisibleQuestionnaires(DateTime.UtcNow) : new List(); } private void SelectQuestionnaire(QuestionnaireInfo info) { if (info == null) { ShowEmptyState(L(TextNoAvailableQuestionnaires)); return; } _questionnaireInfo = info; _currentSheet = QuestionnaireAnswerStore.Instance.GetLatestAnswerSheet(info.QuestionnaireId); if (info.GetEffectiveStatus(DateTime.UtcNow) == QuestionnaireStatus.Expired && !_showExpired) { _showExpired = true; BuildQuestionnaireList(); } UpdateListSelection(); ShowDetail(); } private void BuildQuestionnaireList() { if (_sidebarContent == null) return; DestroyChildren(_sidebarContent); _listItems.Clear(); var items = GetVisibleQuestionnaires(); var featured = items.Where(item => item.GetEffectiveStatus(DateTime.UtcNow) == QuestionnaireStatus.Featured).ToList(); var longTerm = items.Where(item => item.GetEffectiveStatus(DateTime.UtcNow) == QuestionnaireStatus.LongTerm).ToList(); var expired = items.Where(item => item.GetEffectiveStatus(DateTime.UtcNow) == QuestionnaireStatus.Expired).ToList(); CreateSectionHeader(_sidebarContent, L(TextCurrentQuestionnaire)); CreateQuestionnaireListItems(featured, L(TextNoCurrentQuestionnaire)); CreateDivider(_sidebarContent); CreateSectionHeader(_sidebarContent, L(TextLongTermQuestionnaire)); CreateQuestionnaireListItems(longTerm, L(TextNoLongTermQuestionnaires)); CreateDivider(_sidebarContent); CreateExpiredHeader(expired.Count); if (_showExpired) { CreateQuestionnaireListItems(expired, L(TextNoPastQuestionnaires)); } else if (expired.Count > 0) { var hint = CreateText(_sidebarContent, "ExpiredHiddenHint", L(TextPastQuestionnairesCollapsed, expired.Count), 20f, InkMutedColor, TextAlignmentOptions.Left); hint.enableWordWrapping = true; } if (items.Count == 0) { var empty = CreateText(_sidebarContent, "EmptyList", L(TextNoQuestionnairesToDisplay), 20f, InkMutedColor, TextAlignmentOptions.Left); empty.enableWordWrapping = true; } } private void CreateQuestionnaireListItems(List infos, string emptyText) { if (infos == null || infos.Count == 0) { var empty = CreateText(_sidebarContent, "Empty", emptyText, 20f, InkMutedColor, TextAlignmentOptions.Left); empty.enableWordWrapping = true; return; } foreach (var info in infos) { CreateQuestionnaireListItem(info); } } private void CreateQuestionnaireListItem(QuestionnaireInfo info) { var row = InstantiateTemplate(QuestionnaireItemTemplate, _sidebarContent, "QuestionnaireItem"); if (row == null) { row = new GameObject("QuestionnaireItem", typeof(RectTransform), typeof(Image), typeof(Button), typeof(VerticalLayoutGroup), typeof(LayoutElement)); row.transform.SetParent(_sidebarContent, false); } var image = EnsureImage(row.transform, PanelTint, true); ApplySprite(image, SpriteItemButton, PanelTint, false, ItemButtonPixelsPerUnitMultiplier); var layout = row.GetComponent(); if (layout == null) { layout = row.AddComponent(); } layout.padding = new RectOffset(26, 26, 16, 16); layout.spacing = 5f; layout.childControlWidth = true; layout.childControlHeight = true; layout.childForceExpandWidth = true; layout.childForceExpandHeight = false; var layoutElement = row.GetComponent(); if (layoutElement == null) { layoutElement = row.AddComponent(); } layoutElement.minHeight = 98f; layoutElement.preferredHeight = 102f; var button = row.GetComponent