using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Auth;
using Vanguard.Meta;
using Vanguard.State;
namespace Vanguard.UI
{
/// Магазин в Lobby: бустеры и вторая триал-дека.
public sealed class StoreView
{
private readonly MetaService _meta;
private readonly AuthService _auth;
private readonly PlayerState _player;
private readonly Action _onBack;
private readonly RectTransform _root;
private readonly Text _balances;
private readonly Text _status;
private readonly Button _goldButton;
private readonly Button _tokenButton;
private readonly RectTransform _trialContent;
private readonly RectTransform _confirm;
private Text _confirmText;
private readonly RectTransform _result;
private Text _resultTitle;
private RectTransform _resultCards;
private StoreResponse _store;
private Func _confirmAction;
private bool _busy;
private bool _loading;
public StoreView(
Transform parent,
MetaService meta,
AuthService auth,
PlayerState player,
Action onBack)
{
_meta = meta;
_auth = auth;
_player = player;
_onBack = onBack;
_root = UiKit.CreatePanel(parent, "StoreScreen", new Color(0.018f, 0.03f, 0.06f, 0.94f));
var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.06f, 0.08f, 0.13f, 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.02f, 0.03f, 0.06f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(44f, 30f);
body.offsetMax = new Vector2(-44f, -110f);
UiKit.AddVerticalLayout(body, 8, 12);
var booster = UiKit.CreatePanel(body, "Booster", new Color(0.07f, 0.10f, 0.17f, 1f));
UiKit.SetPreferredHeight(booster, 180f);
UiKit.AddVerticalLayout(booster, 20, 8);
var boosterTitle = UiKit.CreateText(
booster, "Title", "Бустер BT01", 26, new Color(0.88f, 0.94f, 1f), TextAnchor.MiddleLeft);
boosterTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(boosterTitle, 34f);
var boosterHint = UiKit.CreateText(
booster,
"Hint",
"5 карт: 4 C + 1 R+ · duplicate protection · 100 Gold или 1 токен.",
18,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(boosterHint, 28f);
var buttons = UiKit.CreatePanel(booster, "Buttons", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(buttons, 54f);
UiKit.AddHorizontalLayout(buttons, 0, 12);
_goldButton = UiKit.CreateButton(
buttons, "BuyGold", "Купить за 100 Gold", () => AskBooster("gold"), 19);
UiKit.SetPreferredHeight(_goldButton, 52f);
_tokenButton = UiKit.CreateButton(
buttons, "BuyToken", "Купить за 1 токен", () => AskBooster("token"), 19);
UiKit.SetPreferredHeight(_tokenButton, 52f);
var trialTitle = UiKit.CreateText(
body, "TrialTitle", "Триал-деки", 24, Color.white, TextAnchor.MiddleLeft);
trialTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(trialTitle, 34f);
var scroll = UiKit.CreateScrollView(body, "TrialScroll", out _trialContent, 8f);
var scrollElement = scroll.gameObject.AddComponent();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 160f;
_status = UiKit.CreateText(
body, "Status", string.Empty, 18,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_status, 34f);
_confirm = BuildConfirm(_root);
_result = BuildResult(_root);
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
_player.Changed += OnPlayerChanged;
}
public void Show()
{
UiKit.SetActive(_root, true);
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
OnPlayerChanged();
_ = LoadAsync();
}
public void Hide()
{
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
UiKit.SetActive(_root, false);
}
public void Tick()
{
}
private async Task LoadAsync()
{
if (_loading)
return;
_loading = true;
SetStatus("Загружаем магазин…", new Color(0.62f, 0.68f, 0.82f));
SetButtons(false);
try
{
await _auth.EnsureAccessAsync();
_store = await _meta.GetStoreAsync();
RebuildTrials(_store.TrialDecks);
_goldButton.GetComponentInChildren().text =
"Купить за " + _store.BoosterPriceGold + " Gold";
SetStatus("Выберите покупку.", 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;
SetButtons(true);
}
}
private void RebuildTrials(IReadOnlyList decks)
{
ClearChildren(_trialContent);
if (decks == null || decks.Count == 0)
{
var empty = UiKit.CreateText(_trialContent, "Empty", "Триал-деки недоступны.", 18,
new Color(0.62f, 0.68f, 0.82f));
UiKit.SetPreferredHeight(empty, 80f);
return;
}
foreach (var deck in decks)
AddTrialRow(deck);
}
private void AddTrialRow(StoreTrialDeckDto deck)
{
var row = UiKit.CreatePanel(_trialContent, "Trial_" + deck.Id, new Color(0.07f, 0.09f, 0.14f, 1f));
var rowElement = row.gameObject.AddComponent();
rowElement.preferredHeight = 92f;
UiKit.AddHorizontalLayout(row, 16, 14);
var info = UiKit.CreatePanel(row, "Info", new Color(0f, 0f, 0f, 0f));
var infoElement = info.gameObject.AddComponent();
infoElement.flexibleWidth = 1f;
infoElement.preferredHeight = 60f;
UiKit.AddVerticalLayout(info, 0, 2);
var name = UiKit.CreateText(
info, "Name", deck.Name, 21, Color.white, TextAnchor.MiddleLeft);
name.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(name, 28f);
var details = UiKit.CreateText(
info,
"Details",
(string.IsNullOrEmpty(deck.Clan) ? "—" : deck.Clan)
+ " · " + deck.CardCount + " карт · "
+ (deck.Owned ? "в коллекции" : "цена " + deck.PriceGold + " Gold"),
16,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(details, 24f);
if (deck.Owned)
{
var owned = UiKit.CreateText(
row, "Owned", "Уже есть", 17,
new Color(0.45f, 0.85f, 0.55f));
var ownedElement = owned.gameObject.AddComponent();
ownedElement.preferredWidth = 120f;
ownedElement.preferredHeight = 40f;
}
else
{
var buy = UiKit.CreateButton(
row,
"Buy",
"Купить за " + deck.PriceGold + " Gold",
() => AskTrial(deck),
17);
var buyElement = buy.gameObject.AddComponent();
buyElement.preferredWidth = 240f;
buyElement.preferredHeight = 46f;
buyElement.flexibleWidth = 0f;
}
}
private void AskBooster(string payment)
{
if (_store == null || _busy)
return;
var price = payment == "gold"
? _store.BoosterPriceGold + " Gold"
: "1 токен";
AskConfirm(
"Открыть бустер BT01 за " + price + "?",
() => BuyBoosterAsync(payment));
}
private void AskTrial(StoreTrialDeckDto deck)
{
if (_busy)
return;
AskConfirm(
"Купить «" + deck.Name + "» за " + deck.PriceGold + " Gold?",
() => BuyTrialAsync(deck));
}
private void AskConfirm(string text, Func action)
{
_confirmText.text = text;
_confirmAction = action;
_confirm.SetAsLastSibling();
_confirm.gameObject.SetActive(true);
}
private async void OnConfirmYes()
{
if (_confirmAction == null)
return;
var action = _confirmAction;
_confirmAction = null;
_confirm.gameObject.SetActive(false);
_busy = true;
SetButtons(false);
SetStatus("Выполняем покупку…", new Color(0.62f, 0.68f, 0.82f));
try
{
await action();
}
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
{
_busy = false;
SetButtons(true);
}
}
private async Task BuyBoosterAsync(string payment)
{
var result = await _meta.BuyBoosterAsync(payment);
SetStatus("Бустер открыт!", new Color(0.45f, 0.85f, 0.55f));
ShowResult(result);
}
private async Task BuyTrialAsync(StoreTrialDeckDto deck)
{
await _meta.BuyTrialDeckAsync(deck.Id);
SetStatus("«" + deck.Name + "» добавлена в коллекцию.", new Color(0.45f, 0.85f, 0.55f));
await LoadAsync();
}
private void ShowResult(BoosterResultDto result)
{
ClearChildren(_resultCards);
_resultTitle.text = "Бустер открыт · " + result.Cards.Count + " карт";
foreach (var card in result.Cards)
{
var panel = UiKit.CreatePanel(_resultCards, "Card_" + card.PrintingId, new Color(0.09f, 0.12f, 0.20f, 1f));
var element = panel.gameObject.AddComponent();
element.preferredWidth = 210f;
element.preferredHeight = 280f;
element.flexibleWidth = 0f;
UiKit.AddVerticalLayout(panel, 14, 8);
var printing = UiKit.CreateText(
panel, "Printing", card.PrintingId, 18, Color.white);
printing.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(printing, 50f);
var name = UiKit.CreateText(
panel, "Name", card.NameId, 16,
new Color(0.70f, 0.78f, 0.94f));
name.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(name, 60f);
var rarity = UiKit.CreateText(
panel, "Rarity", card.Rarity, 20,
new Color(0.45f, 0.80f, 1f));
UiKit.SetPreferredHeight(rarity, 30f);
if (card.IsNew)
{
var isNew = UiKit.CreateText(
panel, "New", "NEW", 18,
new Color(1f, 0.78f, 0.32f));
isNew.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(isNew, 30f);
}
}
_result.SetAsLastSibling();
_result.gameObject.SetActive(true);
}
private void OnResultClosed()
{
_result.gameObject.SetActive(false);
_ = LoadAsync();
}
private RectTransform BuildConfirm(Transform parent)
{
var overlay = UiKit.CreatePanel(parent, "StoreConfirm", new Color(0f, 0f, 0f, 0.75f));
var card = UiKit.CreateCenteredCard(
overlay, "ConfirmCard", new Vector2(620f, 280f),
new Color(0.08f, 0.10f, 0.16f, 1f));
UiKit.AddVerticalLayout(card, 30, 14);
var title = UiKit.CreateText(card, "Title", "Подтвердите покупку", 27, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 40f);
_confirmText = UiKit.CreateText(
card, "Text", string.Empty, 19,
new Color(0.72f, 0.78f, 0.92f));
UiKit.SetPreferredHeight(_confirmText, 70f);
var yes = UiKit.CreateButton(card, "Yes", "Купить", OnConfirmYes, 20);
UiKit.SetPreferredHeight(yes, 48f);
var no = UiKit.CreateButton(
card, "No", "Отмена",
() => { _confirmAction = null; _confirm.gameObject.SetActive(false); },
20);
UiKit.SetPreferredHeight(no, 44f);
return overlay;
}
private RectTransform BuildResult(Transform parent)
{
var overlay = UiKit.CreatePanel(parent, "BoosterResult", new Color(0f, 0f, 0f, 0.82f));
var card = UiKit.CreateCenteredCard(
overlay, "ResultCard", new Vector2(1400f, 520f),
new Color(0.06f, 0.08f, 0.14f, 1f));
UiKit.AddVerticalLayout(card, 28, 14);
_resultTitle = UiKit.CreateText(
card, "Title", "Бустер открыт", 28, Color.white);
_resultTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_resultTitle, 40f);
var row = UiKit.CreatePanel(card, "Cards", new Color(0f, 0f, 0f, 0f));
var rowElement = row.gameObject.AddComponent();
rowElement.flexibleHeight = 1f;
rowElement.minHeight = 300f;
UiKit.AddHorizontalLayout(row, 0, 14);
_resultCards = row;
var close = UiKit.CreateButton(card, "Close", "Забрать", OnResultClosed, 21);
UiKit.SetPreferredHeight(close, 52f);
return overlay;
}
private void SetButtons(bool interactable)
{
if (_goldButton != null)
_goldButton.interactable = interactable;
if (_tokenButton != null)
_tokenButton.interactable = interactable;
}
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;
}
private static void ClearChildren(Transform parent)
{
for (var i = parent.childCount - 1; i >= 0; i--)
UnityEngine.Object.Destroy(parent.GetChild(i).gameObject);
}
}
}