using System; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.UI; using Vanguard.Api; using Vanguard.App; using Vanguard.Auth; namespace Vanguard.UI { /// Экран входа/регистрации и выбора адреса сервера. public sealed class LoginView { private readonly AppConfig _config; private readonly AuthService _auth; private readonly Action _onAuthenticated; private readonly RectTransform _root; private readonly InputField _serverUrl; private readonly InputField _userName; private readonly InputField _password; private readonly Button _loginButton; private readonly Button _registerButton; private readonly Text _status; private bool _busy; public LoginView( Transform parent, AppConfig config, AuthService auth, Action onAuthenticated) { _config = config; _auth = auth; _onAuthenticated = onAuthenticated; _root = UiKit.CreatePanel(parent, "LoginScreen", new Color(0.01f, 0.02f, 0.04f, 0.28f)); // Мягкое неоновое свечение за панелью; 3D-стол остаётся виден по краям. UiKit.CreateCenteredCard( _root, "LoginGlow", new Vector2(760f, 900f), new Color(0.04f, 0.22f, 0.80f, 0.12f)); var card = UiKit.CreateCenteredCard( _root, "LoginCard", new Vector2(620f, 820f), new Color(0.018f, 0.032f, 0.065f, 0.92f)); var layout = UiKit.AddVerticalLayout(card, 32, 10); layout.childAlignment = TextAnchor.MiddleCenter; var eyebrow = UiKit.CreateText( card, "Eyebrow", "VANGUARD // NETWORK", 13, new Color(0.35f, 0.72f, 1f)); UiKit.SetPreferredHeight(eyebrow, 20f); var title = UiKit.CreateText( card, "Title", "VANGUARD", 56, new Color(0.88f, 0.94f, 1f)); title.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(title, 66f); var subtitle = UiKit.CreateText( card, "Subtitle", "RE:MASTER · СЕТЕВОЙ КЛИЕНТ", 18, new Color(0.52f, 0.64f, 0.86f)); UiKit.SetPreferredHeight(subtitle, 30f); var accent = UiKit.CreatePanel(card, "Accent", new Color(0.12f, 0.52f, 1f, 0.9f)); UiKit.SetPreferredHeight(accent, 3f); AddCaption(card, "Сервер"); _serverUrl = UiKit.CreateInput(card, "ServerUrl", "http://192.168.31.158:18080"); _serverUrl.text = _config.ServerBaseUrl; UiKit.SetPreferredHeight(_serverUrl, 54f); AddCaption(card, "Имя игрока"); _userName = UiKit.CreateInput(card, "UserName", "3–32 символа"); UiKit.SetPreferredHeight(_userName, 54f); AddCaption(card, "Пароль"); _password = UiKit.CreateInput(card, "Password", "8–128 символов", password: true); UiKit.SetPreferredHeight(_password, 54f); _loginButton = UiKit.CreateButton(card, "LoginButton", "ВОЙТИ", OnLoginClicked, 22); TintButton(_loginButton, new Color(0.10f, 0.42f, 0.95f, 1f), Color.white); UiKit.SetPreferredHeight(_loginButton, 56f); _registerButton = UiKit.CreateButton( card, "RegisterButton", "СОЗДАТЬ АККАУНТ", OnRegisterClicked, 19); TintButton( _registerButton, new Color(0.05f, 0.10f, 0.18f, 0.95f), new Color(0.48f, 0.76f, 1f)); UiKit.SetPreferredHeight(_registerButton, 48f); _status = UiKit.CreateText( card, "Status", string.Empty, 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.UpperCenter); _status.horizontalOverflow = HorizontalWrapMode.Wrap; UiKit.SetPreferredHeight(_status, 64f); var footer = UiKit.CreateText( card, "Footer", "v" + Application.version + " · стенд 192.168.31.158:18080 · localhost:5080", 14, new Color(0.38f, 0.46f, 0.62f)); UiKit.SetPreferredHeight(footer, 24f); var language = UiKit.CreateLanguageButton(_root, "Language"); var languageRect = language.GetComponent(); languageRect.anchorMin = new Vector2(1f, 1f); languageRect.anchorMax = new Vector2(1f, 1f); languageRect.pivot = new Vector2(1f, 1f); languageRect.anchoredPosition = new Vector2(-24f, -24f); languageRect.sizeDelta = new Vector2(72f, 40f); } public void Show(string status = null) { UiKit.SetActive(_root, true); if (!string.IsNullOrEmpty(status)) SetStatus(status, new Color(0.70f, 0.75f, 0.86f)); } public void Hide() => UiKit.SetActive(_root, false); private static void AddCaption(Transform parent, string text) { var caption = UiKit.CreateText( parent, "Caption_" + text, text, 16, new Color(0.55f, 0.65f, 0.82f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(caption, 22f); } private static void TintButton(Button button, Color background, Color label) { var image = button.GetComponent(); if (image != null) image.color = background; var text = button.GetComponentInChildren(); if (text != null) text.color = label; } private async void OnLoginClicked() { await SubmitAsync(register: false); } private async void OnRegisterClicked() { await SubmitAsync(register: true); } private async Task SubmitAsync(bool register) { if (_busy) return; var userName = (_userName.text ?? string.Empty).Trim(); var password = _password.text ?? string.Empty; if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) { SetStatus("Введите имя игрока и пароль.", new Color(1f, 0.70f, 0.35f)); return; } _config.SetServerBaseUrl(_serverUrl.text); _serverUrl.text = _config.ServerBaseUrl; SetBusy(true, register ? "Создаём аккаунт…" : "Подключаемся…"); try { if (register) await _auth.RegisterAsync(userName, password); else await _auth.LoginAsync(userName, password); _password.text = string.Empty; SetStatus("Готово.", new Color(0.45f, 0.85f, 0.55f)); _onAuthenticated(); } catch (ApiException exception) { SetStatus(FormatError(exception), new Color(1f, 0.55f, 0.45f)); } catch (Exception exception) { Debug.LogException(exception); var detail = exception.Message ?? string.Empty; if (detail.IndexOf("Insecure connection", StringComparison.OrdinalIgnoreCase) >= 0) { SetStatus( "Unity блокирует HTTP. Player Settings → Allow downloads over HTTP = Always, затем перезапусти Unity.", new Color(1f, 0.55f, 0.45f)); } else { SetStatus( "Не удалось связаться: " + detail, new Color(1f, 0.55f, 0.45f)); } } finally { SetBusy(false, null); } } private static string FormatError(ApiException exception) { if (exception.StatusCode == 0) return "Нет связи с сервером. Проверьте адрес и запущен ли сервер."; if (exception.Violations.Count > 0) { var details = string.Join("\n", exception.Violations.Take(3).Select(item => item.Message)); return exception.Message + "\n" + details; } return string.IsNullOrEmpty(exception.Message) ? "Сервер вернул ошибку " + exception.StatusCode + "." : exception.Message; } private void SetBusy(bool busy, string status) { if (_loginButton == null || _registerButton == null || _serverUrl == null || _userName == null || _password == null) return; _busy = busy; _loginButton.interactable = !busy; _registerButton.interactable = !busy; _serverUrl.interactable = !busy; _userName.interactable = !busy; _password.interactable = !busy; if (status != null) SetStatus(status, new Color(0.70f, 0.75f, 0.86f)); } private void SetStatus(string text, Color color) { if (_status == null) return; _status.text = text ?? string.Empty; _status.color = color; } } }