428 lines
16 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|