using System; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.UI; using Vanguard.Api; using Vanguard.Api.Contracts; using Vanguard.Auth; using Vanguard.Meta; namespace Vanguard.UI { /// Первый вход: выбор одной из двух бесплатных триал-дек. public sealed class OnboardingView { private readonly AuthService _auth; private readonly MetaService _meta; private readonly Action _onCompleted; private readonly Action _onLoggedOut; private readonly RectTransform _root; private readonly RectTransform _deckRow; private readonly Text _status; private readonly Button _retry; private readonly Button _logout; private bool _loading; public OnboardingView( Transform parent, AuthService auth, MetaService meta, Action onCompleted, Action onLoggedOut) { _auth = auth; _meta = meta; _onCompleted = onCompleted; _onLoggedOut = onLoggedOut; _root = UiKit.CreatePanel(parent, "OnboardingScreen", new Color(0.035f, 0.045f, 0.065f, 1f)); var card = UiKit.CreateCenteredCard( _root, "OnboardingCard", new Vector2(1320f, 900f), new Color(0.07f, 0.085f, 0.115f, 1f)); UiKit.AddVerticalLayout(card, 34, 12); var title = UiKit.CreateText( card, "Title", "Стартовый набор", 46, Color.white); title.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(title, 58f); var subtitle = UiKit.CreateText( card, "Subtitle", "Выберите одну триал-деку. 50 карт попадут в коллекцию, плюс 5 токенов на бустеры.", 20, new Color(0.68f, 0.74f, 0.88f)); UiKit.SetPreferredHeight(subtitle, 54f); _deckRow = UiKit.CreatePanel( card, "DeckRow", new Color(0.05f, 0.06f, 0.085f, 1f)); UiKit.SetPreferredHeight(_deckRow, 500f); UiKit.AddHorizontalLayout(_deckRow, 16, 18); _status = UiKit.CreateText( card, "Status", string.Empty, 18, new Color(0.68f, 0.74f, 0.88f), TextAnchor.MiddleCenter); UiKit.SetPreferredHeight(_status, 38f); _retry = UiKit.CreateButton(card, "Retry", "Повторить загрузку", OnRetryClicked, 18); UiKit.SetPreferredHeight(_retry, 42f); _retry.gameObject.SetActive(false); _logout = UiKit.CreateButton(card, "Logout", "Выйти из аккаунта", OnLogoutClicked, 18); UiKit.SetPreferredHeight(_logout, 42f); _root.gameObject.SetActive(false); } public void Show() { UiKit.SetActive(_root, true); _ = LoadAsync(); } public void Hide() => UiKit.SetActive(_root, false); private async Task LoadAsync() { if (_loading) return; _loading = true; _retry.gameObject.SetActive(false); _logout.interactable = false; ClearDeckRow(); SetStatus("Загружаем триал-деки…", new Color(0.68f, 0.74f, 0.88f)); try { var store = await _meta.GetStoreAsync(); BuildDecks(store); SetStatus("Выберите деку — выбор делается один раз.", new Color(0.68f, 0.74f, 0.88f)); } catch (ApiException exception) { SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f)); _retry.gameObject.SetActive(true); } catch (Exception exception) { Debug.LogException(exception); SetStatus("Не удалось загрузить триал-деки. Сервер недоступен?", new Color(1f, 0.55f, 0.45f)); _retry.gameObject.SetActive(true); } finally { _loading = false; _logout.interactable = true; } } private void BuildDecks(StoreResponse store) { if (store.TrialDecks == null || store.TrialDecks.Count == 0) { SetStatus("Сервер не вернул ни одной триал-деки.", new Color(1f, 0.55f, 0.45f)); return; } foreach (var deck in store.TrialDecks) BuildDeck(deck); } private void BuildDeck(StoreTrialDeckDto deck) { var card = UiKit.CreatePanel( _deckRow, "Trial_" + deck.Id, new Color(0.09f, 0.11f, 0.15f, 1f)); var element = card.gameObject.AddComponent(); element.flexibleWidth = 1f; element.preferredHeight = 460f; UiKit.AddVerticalLayout(card, 24, 10); var name = UiKit.CreateText(card, "Name", deck.Name, 30, Color.white, TextAnchor.MiddleLeft); name.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(name, 42f); var clan = UiKit.CreateText( card, "Clan", "Клан: " + (string.IsNullOrEmpty(deck.Clan) ? "—" : deck.Clan), 20, new Color(0.68f, 0.74f, 0.88f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(clan, 30f); var count = UiKit.CreateText( card, "Count", deck.CardCount + " карт · " + deck.Cards.Count + " уникальных", 18, new Color(0.60f, 0.67f, 0.80f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(count, 28f); var preview = UiKit.CreateText( card, "Preview", BuildPreview(deck), 16, new Color(0.52f, 0.60f, 0.74f), TextAnchor.UpperLeft); preview.horizontalOverflow = HorizontalWrapMode.Wrap; UiKit.SetPreferredHeight(preview, 150f, 1f); if (deck.Owned) { var owned = UiKit.CreateText( card, "Owned", "Уже в коллекции", 18, new Color(0.45f, 0.85f, 0.55f)); UiKit.SetPreferredHeight(owned, 28f); } var choose = UiKit.CreateButton( card, "Choose", deck.Owned ? "Дека уже получена" : "Выбрать бесплатно", () => OnChooseClicked(deck), 21); choose.interactable = !deck.Owned; UiKit.SetPreferredHeight(choose, 54f); } private static string BuildPreview(StoreTrialDeckDto deck) { if (deck.Cards == null || deck.Cards.Count == 0) return "Состав появится после выбора."; var lines = deck.Cards .Take(8) .Select(card => "• " + card.PrintingId + " ×" + card.Count); return string.Join("\n", lines); } private async void OnChooseClicked(StoreTrialDeckDto deck) { if (_loading) return; _loading = true; _retry.gameObject.SetActive(false); SetStatus("Активируем " + deck.Name + "…", new Color(0.68f, 0.74f, 0.88f)); try { await _meta.OnboardAsync(deck.Id); _onCompleted(); } 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 OnRetryClicked() { _ = LoadAsync(); } private async void OnLogoutClicked() { _logout.interactable = false; await _auth.LogoutAsync(); _onLoggedOut(); } private void ClearDeckRow() { for (var i = _deckRow.childCount - 1; i >= 0; i--) UnityEngine.Object.Destroy(_deckRow.GetChild(i).gameObject); } private void SetStatus(string text, Color color) { _status.text = text ?? string.Empty; _status.color = color; } } }