357 lines
13 KiB
C#
357 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using Vanguard.Api;
|
||
using Vanguard.Api.Contracts;
|
||
using Vanguard.Meta;
|
||
using Vanguard.State;
|
||
|
||
namespace Vanguard.UI
|
||
{
|
||
/// <summary>Список колод и быстрый сборщик стартовой колоды из триал-деки.</summary>
|
||
public sealed class DecksView : IDisposable
|
||
{
|
||
private readonly MetaService _meta;
|
||
private readonly PlayerState _player;
|
||
private readonly Action _onBack;
|
||
private readonly Action<DeckDto> _onPlay;
|
||
private bool _disposed;
|
||
|
||
private readonly RectTransform _root;
|
||
private RectTransform _content;
|
||
private readonly Text _balances;
|
||
private readonly Text _status;
|
||
private readonly Button _createButton;
|
||
private readonly RectTransform _confirm;
|
||
private Text _confirmText;
|
||
private DeckDto _pendingDelete;
|
||
private bool _loading;
|
||
|
||
public DecksView(
|
||
Transform parent,
|
||
MetaService meta,
|
||
PlayerState player,
|
||
Action onBack,
|
||
Action<DeckDto> onPlay)
|
||
{
|
||
_meta = meta;
|
||
_player = player;
|
||
_onBack = onBack;
|
||
_onPlay = onPlay;
|
||
|
||
_root = UiKit.CreatePanel(parent, "DecksScreen", new Color(0.035f, 0.045f, 0.065f, 1f));
|
||
|
||
var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.07f, 0.085f, 0.115f, 1f));
|
||
top.anchorMin = new Vector2(0f, 1f);
|
||
top.anchorMax = new Vector2(1f, 1f);
|
||
top.pivot = new Vector2(0.5f, 1f);
|
||
top.anchoredPosition = Vector2.zero;
|
||
top.sizeDelta = new Vector2(0f, 88f);
|
||
UiKit.AddHorizontalLayout(top, 22, 16);
|
||
|
||
var back = UiKit.CreateButton(top, "Back", "← Назад", () => _onBack(), 18);
|
||
UiKit.SetPreferredHeight(back, 44f, 0f);
|
||
|
||
var title = UiKit.CreateText(top, "Title", "Колоды", 32, Color.white, TextAnchor.MiddleLeft);
|
||
title.fontStyle = FontStyle.Bold;
|
||
UiKit.SetPreferredHeight(title, 46f);
|
||
|
||
_balances = UiKit.CreateText(
|
||
top, "Balances", string.Empty, 18,
|
||
new Color(0.70f, 0.76f, 0.90f), TextAnchor.MiddleRight);
|
||
UiKit.SetPreferredHeight(_balances, 36f);
|
||
|
||
var body = UiKit.CreatePanel(_root, "Body", new Color(0.035f, 0.045f, 0.065f, 0f));
|
||
body.anchorMin = Vector2.zero;
|
||
body.anchorMax = Vector2.one;
|
||
body.offsetMin = new Vector2(40f, 28f);
|
||
body.offsetMax = new Vector2(-40f, -110f);
|
||
UiKit.AddVerticalLayout(body, 8, 10);
|
||
|
||
var hint = UiKit.CreateText(
|
||
body,
|
||
"Hint",
|
||
"Стартовая колода собирается из выбранной триал-деки: 50 карт, состав уже проверен сервером.",
|
||
17,
|
||
new Color(0.58f, 0.65f, 0.78f),
|
||
TextAnchor.MiddleLeft);
|
||
UiKit.SetPreferredHeight(hint, 26f);
|
||
|
||
var scroll = UiKit.CreateScrollView(body, "DecksScroll", out _content, 8f);
|
||
var scrollElement = scroll.gameObject.AddComponent<LayoutElement>();
|
||
scrollElement.flexibleHeight = 1f;
|
||
scrollElement.minHeight = 180f;
|
||
|
||
var actions = UiKit.CreatePanel(body, "Actions", new Color(0.05f, 0.06f, 0.085f, 0f));
|
||
UiKit.SetPreferredHeight(actions, 56f);
|
||
UiKit.AddHorizontalLayout(actions, 0, 12);
|
||
|
||
_createButton = UiKit.CreateButton(
|
||
actions,
|
||
"CreateFromTrial",
|
||
"Собрать из триал-деки",
|
||
OnCreateFromTrialClicked,
|
||
19);
|
||
UiKit.SetPreferredHeight(_createButton, 52f);
|
||
|
||
_status = UiKit.CreateText(
|
||
body, "Status", string.Empty, 18,
|
||
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
|
||
UiKit.SetPreferredHeight(_status, 34f);
|
||
|
||
_confirm = BuildConfirm(parent);
|
||
_confirm.gameObject.SetActive(false);
|
||
|
||
_root.gameObject.SetActive(false);
|
||
_player.Changed += OnPlayerChanged;
|
||
}
|
||
|
||
public void Show()
|
||
{
|
||
UiKit.SetActive(_root, true);
|
||
_confirm.gameObject.SetActive(false);
|
||
OnPlayerChanged();
|
||
_ = LoadAsync();
|
||
}
|
||
|
||
public void Hide()
|
||
{
|
||
UiKit.SetActive(_root, false);
|
||
_confirm.gameObject.SetActive(false);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
if (_disposed)
|
||
return;
|
||
|
||
_disposed = true;
|
||
_player.Changed -= OnPlayerChanged;
|
||
}
|
||
|
||
private RectTransform BuildConfirm(Transform parent)
|
||
{
|
||
var overlay = UiKit.CreatePanel(parent, "DeleteDeckConfirm", new Color(0f, 0f, 0f, 0.72f));
|
||
var card = UiKit.CreateCenteredCard(
|
||
overlay, "ConfirmCard", new Vector2(620f, 280f),
|
||
new Color(0.08f, 0.10f, 0.14f, 1f));
|
||
UiKit.AddVerticalLayout(card, 30, 12);
|
||
|
||
var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, Color.white);
|
||
title.fontStyle = FontStyle.Bold;
|
||
UiKit.SetPreferredHeight(title, 42f);
|
||
|
||
_confirmText = UiKit.CreateText(
|
||
card, "Text", string.Empty, 19,
|
||
new Color(0.72f, 0.78f, 0.92f),
|
||
TextAnchor.MiddleCenter);
|
||
UiKit.SetPreferredHeight(_confirmText, 80f);
|
||
|
||
var yes = UiKit.CreateButton(card, "Yes", "Удалить", OnConfirmDelete, 20);
|
||
UiKit.SetPreferredHeight(yes, 48f);
|
||
|
||
var no = UiKit.CreateButton(card, "No", "Отмена", HideConfirm, 20);
|
||
UiKit.SetPreferredHeight(no, 44f);
|
||
|
||
overlay.gameObject.SetActive(false);
|
||
return overlay;
|
||
}
|
||
|
||
private async Task LoadAsync()
|
||
{
|
||
if (_loading)
|
||
return;
|
||
|
||
_loading = true;
|
||
SetStatus("Загружаем колоды…", new Color(0.62f, 0.68f, 0.82f));
|
||
|
||
try
|
||
{
|
||
var decks = await _meta.GetDecksAsync();
|
||
if (_disposed)
|
||
return;
|
||
|
||
Rebuild(decks);
|
||
SetStatus(
|
||
decks.Count == 0 ? "Колод пока нет." : "Колод: " + decks.Count + ".",
|
||
new Color(0.62f, 0.68f, 0.82f));
|
||
}
|
||
catch (ApiException exception)
|
||
{
|
||
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
Debug.LogException(exception);
|
||
SetStatus("Не удалось загрузить колоды.", new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
finally
|
||
{
|
||
_loading = false;
|
||
}
|
||
}
|
||
|
||
private void Rebuild(IReadOnlyList<DeckDto> decks)
|
||
{
|
||
for (var i = _content.childCount - 1; i >= 0; i--)
|
||
UnityEngine.Object.Destroy(_content.GetChild(i).gameObject);
|
||
|
||
foreach (var deck in decks)
|
||
AddRow(deck);
|
||
}
|
||
|
||
private void AddRow(DeckDto deck)
|
||
{
|
||
var row = UiKit.CreatePanel(_content, "Deck_" + deck.Id, new Color(0.07f, 0.085f, 0.115f, 1f));
|
||
var rowElement = row.gameObject.AddComponent<LayoutElement>();
|
||
rowElement.preferredHeight = 72f;
|
||
UiKit.AddHorizontalLayout(row, 12, 12);
|
||
|
||
var name = UiKit.CreateText(
|
||
row, "Name", deck.Name, 21, Color.white, TextAnchor.MiddleLeft);
|
||
name.fontStyle = FontStyle.Bold;
|
||
var nameElement = name.gameObject.AddComponent<LayoutElement>();
|
||
nameElement.flexibleWidth = 1f;
|
||
nameElement.preferredHeight = 44f;
|
||
|
||
var info = UiKit.CreateText(
|
||
row,
|
||
"Info",
|
||
deck.Cards.Count + " карт · " + deck.Format,
|
||
16,
|
||
new Color(0.58f, 0.65f, 0.78f),
|
||
TextAnchor.MiddleLeft);
|
||
var infoElement = info.gameObject.AddComponent<LayoutElement>();
|
||
infoElement.preferredWidth = 230f;
|
||
infoElement.preferredHeight = 40f;
|
||
|
||
var play = UiKit.CreateButton(row, "Play", "В бой", () => OnPlayClicked(deck), 17);
|
||
var playElement = play.gameObject.AddComponent<LayoutElement>();
|
||
playElement.preferredWidth = 120f;
|
||
playElement.preferredHeight = 46f;
|
||
playElement.flexibleWidth = 0f;
|
||
|
||
var delete = UiKit.CreateButton(row, "Delete", "Удалить", () => ShowConfirm(deck), 16);
|
||
var deleteElement = delete.gameObject.AddComponent<LayoutElement>();
|
||
deleteElement.preferredWidth = 110f;
|
||
deleteElement.preferredHeight = 46f;
|
||
deleteElement.flexibleWidth = 0f;
|
||
}
|
||
|
||
private async void OnCreateFromTrialClicked()
|
||
{
|
||
if (_loading)
|
||
return;
|
||
|
||
_loading = true;
|
||
_createButton.interactable = false;
|
||
SetStatus("Ищем выбранную триал-деку…", new Color(0.62f, 0.68f, 0.82f));
|
||
|
||
try
|
||
{
|
||
var store = await _meta.GetStoreAsync();
|
||
var trial = store.TrialDecks.FirstOrDefault(deck => deck.Owned);
|
||
|
||
if (trial == null)
|
||
{
|
||
SetStatus("Сначала завершите онбординг и выберите триал-деку.", new Color(1f, 0.60f, 0.45f));
|
||
return;
|
||
}
|
||
|
||
var cards = trial.Cards
|
||
.SelectMany(card => Enumerable.Repeat(card.PrintingId, card.Count))
|
||
.ToList();
|
||
|
||
var created = await _meta.CreateDeckAsync(trial.Name + " (старт)", cards);
|
||
SetStatus("Колода «" + created.Name + "» собрана: " + created.Cards.Count + " карт.", new Color(0.45f, 0.85f, 0.55f));
|
||
_loading = false;
|
||
await LoadAsync();
|
||
}
|
||
catch (ApiException exception)
|
||
{
|
||
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
Debug.LogException(exception);
|
||
SetStatus("Не удалось собрать колоду.", new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
finally
|
||
{
|
||
_loading = false;
|
||
_createButton.interactable = true;
|
||
}
|
||
}
|
||
|
||
private void OnPlayClicked(DeckDto deck)
|
||
{
|
||
_onPlay(deck);
|
||
}
|
||
|
||
private void ShowConfirm(DeckDto deck)
|
||
{
|
||
_pendingDelete = deck;
|
||
_confirmText.text = "«" + deck.Name + "» будет удалена без возможности восстановления.";
|
||
_confirm.SetAsLastSibling();
|
||
_confirm.gameObject.SetActive(true);
|
||
}
|
||
|
||
private void HideConfirm()
|
||
{
|
||
_pendingDelete = null;
|
||
_confirm.gameObject.SetActive(false);
|
||
}
|
||
|
||
private async void OnConfirmDelete()
|
||
{
|
||
if (_pendingDelete == null)
|
||
return;
|
||
|
||
var deck = _pendingDelete;
|
||
_confirm.gameObject.SetActive(false);
|
||
SetStatus("Удаляем «" + deck.Name + "»…", new Color(0.62f, 0.68f, 0.82f));
|
||
|
||
try
|
||
{
|
||
await _meta.DeleteDeckAsync(deck.Id);
|
||
SetStatus("Колода удалена.", new Color(0.45f, 0.85f, 0.55f));
|
||
await LoadAsync();
|
||
}
|
||
catch (ApiException exception)
|
||
{
|
||
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
Debug.LogException(exception);
|
||
SetStatus("Не удалось удалить колоду.", new Color(1f, 0.55f, 0.45f));
|
||
}
|
||
finally
|
||
{
|
||
_pendingDelete = null;
|
||
}
|
||
}
|
||
|
||
private void OnPlayerChanged()
|
||
{
|
||
if (_disposed || _balances == null)
|
||
return;
|
||
|
||
_balances.text = "Gold " + _player.Gold + " · Пыль " + _player.Dust
|
||
+ " · Токены " + _player.Tokens;
|
||
}
|
||
|
||
private void SetStatus(string text, Color color)
|
||
{
|
||
if (_disposed || _status == null)
|
||
return;
|
||
|
||
_status.text = text ?? string.Empty;
|
||
_status.color = color;
|
||
}
|
||
}
|
||
}
|