TH1/Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideQuestionnaireView.cs

428 lines
16 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Logic.Multilingual;
using TH1_Core.Events;
using TH1_Logic.Questionnaire;
using TH1Resource;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace TH1_UI.View.Outside
{
public class UIOutsideQuestionnaireView : Base.View
{
private const string DataAssetPath = "DataAssets/QuestionnaireDataAssets";
private const string QuestionPrefabPath = "Prefab/UI/Outside/UIOutsideQuestionnaireQuestion";
private const string OptionPrefabPath = "Prefab/UI/Outside/UIOutsideQuestionnaireOption";
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 ViDelegateAssisstant.Dele OnBtnCloseClick;
private readonly List<UIOutsideQuestionnaireQuestionMono> _questionItems = new List<UIOutsideQuestionnaireQuestionMono>();
private QuestionnaireDataAssets _dataAssets;
private QuestionnaireInfo _questionnaireInfo;
private QuestionnaireAnswerSheet _currentSheet;
protected override void OnInit()
{
base.OnInit();
EnsureLayout();
}
public void SetContent(ShowUIOutsideQuestionnaire evt)
{
EnsureLayout();
LoadAssetsIfNeeded();
_questionnaireInfo = null;
if (_dataAssets != null)
{
if (!string.IsNullOrEmpty(evt.QuestionnaireId))
{
_dataAssets.GetQuestionnaireInfo(evt.QuestionnaireId, out _questionnaireInfo);
}
_questionnaireInfo ??= _dataAssets.GetDefaultQuestionnaire();
}
if (_questionnaireInfo == null)
{
Debug.LogError("[UIOutsideQuestionnaireView] QuestionnaireDataAssets is missing questionnaire data.");
SetStatus(string.Empty);
return;
}
TitleText.text = ResolveText(_questionnaireInfo.Title);
DescriptionText.text = ResolveText(_questionnaireInfo.Description);
SubmitButtonText.text = ResolveText(_questionnaireInfo.SubmitButtonText);
RefillButtonText.text = ResolveText(_questionnaireInfo.ResubmitButtonText);
CloseButtonText.text = ResolveText(_questionnaireInfo.CloseButtonText);
SubmitButton.onClick.RemoveAllListeners();
SubmitButton.onClick.AddListener(OnSubmitClicked);
RefillButton.onClick.RemoveAllListeners();
RefillButton.onClick.AddListener(OnRefillClicked);
CloseButton.onClick.RemoveAllListeners();
CloseButton.onClick.AddListener(OnCloseClicked);
BuildQuestions();
_currentSheet = QuestionnaireAnswerStore.Instance.GetAnswerSheet(_questionnaireInfo.QuestionnaireId);
ApplyPreviousSheet(_currentSheet);
var hasSubmitted = _currentSheet != null;
RefillButton.gameObject.SetActive(hasSubmitted);
SetStatus(hasSubmitted ? ResolveText(_questionnaireInfo.SubmittedMessage) : string.Empty);
}
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;
}
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<TextMeshProUGUI>();
label.text = text;
label.fontSize = fontSize;
label.color = color;
label.alignment = alignment;
label.enableWordWrapping = false;
label.raycastTarget = false;
return label;
}
private void LoadAssetsIfNeeded()
{
if (_dataAssets == null)
{
_dataAssets = ResourceLoader.Load<QuestionnaireDataAssets>(DataAssetPath);
}
if (QuestionPrefab == null)
{
QuestionPrefab = ResourceLoader.Load<GameObject>(QuestionPrefabPath);
}
if (OptionPrefab == null)
{
OptionPrefab = ResourceLoader.Load<GameObject>(OptionPrefabPath);
}
}
private void BuildQuestions()
{
foreach (Transform child in QuestionListContent)
{
Destroy(child.gameObject);
}
_questionItems.Clear();
if (_questionnaireInfo.Questions == null) return;
for (var i = 0; i < _questionnaireInfo.Questions.Count; i++)
{
var question = _questionnaireInfo.Questions[i];
if (question == null) continue;
var item = CreateQuestionItem();
item.SetContent(i + 1, question, OptionPrefab, OnAnswerChanged);
_questionItems.Add(item);
}
}
private UIOutsideQuestionnaireQuestionMono CreateQuestionItem()
{
GameObject go;
if (QuestionPrefab != null)
{
go = Instantiate(QuestionPrefab, QuestionListContent);
}
else
{
go = new GameObject("Question", typeof(RectTransform));
go.transform.SetParent(QuestionListContent, false);
}
var item = go.GetComponent<UIOutsideQuestionnaireQuestionMono>();
if (item == null)
{
item = go.AddComponent<UIOutsideQuestionnaireQuestionMono>();
}
return item;
}
private void ApplyPreviousSheet(QuestionnaireAnswerSheet sheet)
{
if (sheet?.Answers == null) return;
var answerDict = sheet.Answers
.Where(answer => answer != null && !string.IsNullOrEmpty(answer.QuestionId))
.ToDictionary(answer => answer.QuestionId, answer => answer);
foreach (var item in _questionItems)
{
if (item.QuestionInfo == null || string.IsNullOrEmpty(item.QuestionInfo.QuestionId)) continue;
if (answerDict.TryGetValue(item.QuestionInfo.QuestionId, out var answer))
{
item.ApplyAnswer(answer);
}
}
}
private void OnSubmitClicked()
{
if (_questionnaireInfo == null) return;
if (!ValidateRequiredQuestions())
{
SetStatus(ResolveText(_questionnaireInfo.RequiredMessage));
return;
}
var sheet = new QuestionnaireAnswerSheet
{
QuestionnaireId = _questionnaireInfo.QuestionnaireId,
Answers = new List<QuestionnaireAnswer>()
};
foreach (var item in _questionItems)
{
sheet.Answers.Add(item.CreateAnswer());
}
if (QuestionnaireAnswerStore.Instance.SaveAnswerSheet(sheet))
{
_currentSheet = sheet;
RefillButton.gameObject.SetActive(true);
SetStatus(ResolveText(_questionnaireInfo.SubmittedMessage));
}
else
{
SetStatus(ResolveText(_questionnaireInfo.SaveFailedMessage));
}
}
private bool ValidateRequiredQuestions()
{
foreach (var item in _questionItems)
{
if (item.QuestionInfo != null && item.QuestionInfo.Required && !item.HasAnswer())
{
return false;
}
}
return true;
}
private void OnRefillClicked()
{
foreach (var item in _questionItems)
{
item.ClearAnswer();
}
SetStatus(ResolveText(_questionnaireInfo?.RefillHintText));
}
private void OnAnswerChanged()
{
if (!string.IsNullOrEmpty(StatusText?.text) && _currentSheet == null)
{
SetStatus(string.Empty);
}
}
private void OnCloseClicked()
{
OnBtnCloseClick?.Invoke();
}
private void SetStatus(string text)
{
if (StatusText == null) return;
StatusText.text = text ?? string.Empty;
StatusText.gameObject.SetActive(!string.IsNullOrEmpty(StatusText.text));
}
private void EnsureLayout()
{
var rootRect = GetComponent<RectTransform>();
rootRect.anchorMin = Vector2.zero;
rootRect.anchorMax = Vector2.one;
rootRect.offsetMin = Vector2.zero;
rootRect.offsetMax = Vector2.zero;
var canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
{
canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
var background = GetComponent<Image>();
if (background == null)
{
background = gameObject.AddComponent<Image>();
}
background.color = new Color(0.04f, 0.05f, 0.055f, 0.96f);
if (TitleText != null && QuestionListContent != null && SubmitButton != null && CloseButton != null)
{
return;
}
var content = new GameObject("Content", typeof(RectTransform), typeof(VerticalLayoutGroup));
content.transform.SetParent(transform, false);
var contentRect = content.GetComponent<RectTransform>();
contentRect.anchorMin = new Vector2(0.5f, 0.5f);
contentRect.anchorMax = new Vector2(0.5f, 0.5f);
contentRect.pivot = new Vector2(0.5f, 0.5f);
contentRect.sizeDelta = new Vector2(1040f, 700f);
var layout = content.GetComponent<VerticalLayoutGroup>();
layout.padding = new RectOffset(36, 36, 30, 28);
layout.spacing = 14f;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = true;
layout.childForceExpandHeight = false;
TitleText = CreateText(content.transform, "Title", "", 34f, new Color(0.98f, 0.96f, 0.88f, 1f), TextAlignmentOptions.Left);
TitleText.fontStyle = FontStyles.Bold;
TitleText.enableWordWrapping = true;
DescriptionText = CreateText(content.transform, "Description", "", 20f, new Color(0.78f, 0.8f, 0.78f, 1f), TextAlignmentOptions.Left);
DescriptionText.enableWordWrapping = true;
CreateQuestionScroll(content.transform);
StatusText = CreateText(content.transform, "Status", "", 19f, new Color(0.2f, 0.85f, 0.72f, 1f), TextAlignmentOptions.Left);
StatusText.enableWordWrapping = true;
CreateFooter(content.transform);
}
private void CreateQuestionScroll(Transform parent)
{
var scrollRoot = new GameObject("QuestionScroll", typeof(RectTransform), typeof(Image), typeof(ScrollRect), typeof(LayoutElement));
scrollRoot.transform.SetParent(parent, false);
var scrollLayout = scrollRoot.GetComponent<LayoutElement>();
scrollLayout.flexibleHeight = 1f;
scrollLayout.minHeight = 360f;
var image = scrollRoot.GetComponent<Image>();
image.color = new Color(0f, 0f, 0f, 0.18f);
var viewport = new GameObject("Viewport", typeof(RectTransform), typeof(Image), typeof(Mask));
viewport.transform.SetParent(scrollRoot.transform, false);
var viewportRect = viewport.GetComponent<RectTransform>();
viewportRect.anchorMin = Vector2.zero;
viewportRect.anchorMax = Vector2.one;
viewportRect.offsetMin = new Vector2(8f, 8f);
viewportRect.offsetMax = new Vector2(-8f, -8f);
var viewportImage = viewport.GetComponent<Image>();
viewportImage.color = new Color(1f, 1f, 1f, 0.01f);
viewport.GetComponent<Mask>().showMaskGraphic = false;
var content = new GameObject("QuestionListContent", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
content.transform.SetParent(viewport.transform, false);
var contentRect = content.GetComponent<RectTransform>();
contentRect.anchorMin = new Vector2(0f, 1f);
contentRect.anchorMax = new Vector2(1f, 1f);
contentRect.pivot = new Vector2(0.5f, 1f);
contentRect.offsetMin = Vector2.zero;
contentRect.offsetMax = Vector2.zero;
var contentLayout = content.GetComponent<VerticalLayoutGroup>();
contentLayout.spacing = 10f;
contentLayout.childControlWidth = true;
contentLayout.childControlHeight = true;
contentLayout.childForceExpandWidth = true;
contentLayout.childForceExpandHeight = false;
content.GetComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
QuestionListContent = content.transform;
var scrollRect = scrollRoot.GetComponent<ScrollRect>();
scrollRect.viewport = viewportRect;
scrollRect.content = contentRect;
scrollRect.horizontal = false;
scrollRect.vertical = true;
scrollRect.scrollSensitivity = 30f;
scrollRect.movementType = ScrollRect.MovementType.Clamped;
}
private void CreateFooter(Transform parent)
{
var footer = new GameObject("Footer", typeof(RectTransform), typeof(HorizontalLayoutGroup));
footer.transform.SetParent(parent, false);
var layout = footer.GetComponent<HorizontalLayoutGroup>();
layout.spacing = 12f;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = false;
layout.childForceExpandHeight = false;
layout.childAlignment = TextAnchor.MiddleRight;
var spacer = new GameObject("Spacer", typeof(RectTransform), typeof(LayoutElement));
spacer.transform.SetParent(footer.transform, false);
spacer.GetComponent<LayoutElement>().flexibleWidth = 1f;
RefillButton = CreateButton(footer.transform, "RefillButton", out RefillButtonText);
SubmitButton = CreateButton(footer.transform, "SubmitButton", out SubmitButtonText);
CloseButton = CreateButton(footer.transform, "CloseButton", out CloseButtonText);
}
private static Button CreateButton(Transform parent, string objectName, out TextMeshProUGUI label)
{
var go = new GameObject(objectName, typeof(RectTransform), typeof(Image), typeof(Button), typeof(LayoutElement));
go.transform.SetParent(parent, false);
var layout = go.GetComponent<LayoutElement>();
layout.minWidth = 132f;
layout.preferredWidth = 148f;
layout.minHeight = 46f;
layout.preferredHeight = 46f;
var image = go.GetComponent<Image>();
image.color = objectName == "SubmitButton"
? new Color(0.18f, 0.58f, 0.52f, 1f)
: new Color(1f, 1f, 1f, 0.1f);
var button = go.GetComponent<Button>();
button.targetGraphic = image;
label = CreateText(go.transform, "Label", "", 20f, new Color(0.98f, 0.98f, 0.92f, 1f), TextAlignmentOptions.Center);
label.rectTransform.anchorMin = Vector2.zero;
label.rectTransform.anchorMax = Vector2.one;
label.rectTransform.offsetMin = Vector2.zero;
label.rectTransform.offsetMax = Vector2.zero;
return button;
}
}
}