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 { /// Список колод и быстрый сборщик стартовой колоды из триал-деки. public sealed class DecksView { private readonly MetaService _meta; private readonly PlayerState _player; private readonly Action _onBack; 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) { _meta = meta; _player = player; _onBack = onBack; _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(); 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); } 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(); 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 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(); 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(); 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(); infoElement.preferredWidth = 230f; infoElement.preferredHeight = 40f; var play = UiKit.CreateButton(row, "Play", "В бой", () => OnPlayClicked(deck), 17); var playElement = play.gameObject.AddComponent(); playElement.preferredWidth = 120f; playElement.preferredHeight = 46f; playElement.flexibleWidth = 0f; var delete = UiKit.CreateButton(row, "Delete", "Удалить", () => ShowConfirm(deck), 16); var deleteElement = delete.gameObject.AddComponent(); 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) { SetStatus("Очередь и бой подключим следующим шагом. Колода: " + deck.Name + ".", new Color(1f, 0.76f, 0.38f)); } 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() { _balances.text = "Gold " + _player.Gold + " · Пыль " + _player.Dust + " · Токены " + _player.Tokens; } private void SetStatus(string text, Color color) { _status.text = text ?? string.Empty; _status.color = color; } } }