using System; using System.Text; using DG.Tweening; using TMPro; using UnityEngine; public sealed class SettingsPasswordPanel : MonoBehaviour, IControlable { public event Action OnAccepted; public event Action OnCancelled; [SerializeField] private CanvasGroup _canvasGroup; [SerializeField] private TextMeshProUGUI _title; [SerializeField] private TextMeshProUGUI _combination; [SerializeField] private TextMeshProUGUI _hint; [SerializeField] private TextMeshProUGUI _status; [SerializeField] private RectTransform _confirmProgressFill; [SerializeField] private CanvasGroup _confirmationGroup; [SerializeField] private TextMeshProUGUI[] _confirmationOptions; [SerializeField] private float _confirmHoldDuration = 1f; [SerializeField] private float _cancelHoldDuration = 1f; [SerializeField] private float _fadeDuration = 0.25f; [SerializeField] private Color _normalOptionColor = Color.white; [SerializeField] private Color _selectedOptionColor = new Color(1f, 0.85f, 0.1f, 1f); [SerializeField] private Color _errorColor = new Color(1f, 0.25f, 0.25f, 1f); private const int PasswordLength = 6; private const int SaveOptionIndex = 0; private const int ReenterOptionIndex = 1; private const int CancelOptionIndex = 2; private readonly StringBuilder _enteredCombination = new StringBuilder(PasswordLength); private Tween _fadeTween; private PanelPurpose _purpose; private PanelStage _stage; private float _confirmHoldTime; private float _cancelHoldTime; private float _statusHideTime; private int _selectedOptionIndex; private bool _actionReleased; private bool _serviceReleased; private bool _isClosing; public bool ControlsEnabled { get; set; } private void OnDisable() { _fadeTween?.Kill(); _fadeTween = null; ControlsEnabled = false; } public void OpenForSettingsAccess() { _purpose = PanelPurpose.SettingsAccess; Open(PanelStage.VerifyPassword); } public void OpenForPasswordChange() { _purpose = PanelPurpose.PasswordChange; Open(SettingsPasswordStore.HasPassword ? PanelStage.VerifyPassword : PanelStage.EnterNewPassword); } public void UpdateControls() { UpdateStatus(); UpdateCancelInput(); if (_isClosing) return; if (_stage == PanelStage.ConfirmPassword) UpdateConfirmationControls(); else UpdatePasswordControls(); } private void Open(PanelStage initialStage) { _fadeTween?.Kill(); transform.SetAsLastSibling(); _canvasGroup.alpha = 0f; _canvasGroup.blocksRaycasts = true; _isClosing = false; _serviceReleased = false; ControlsEnabled = true; SetStage(initialStage); _fadeTween = _canvasGroup.DOFade(1f, _fadeDuration).SetEase(Ease.OutCubic).SetUpdate(true); _fadeTween.PlayForward(); } private void UpdatePasswordControls() { Vector2Int axis = In.GetAxis(JoystickSide.Both, InteractMode.Discrete); if (_enteredCombination.Length < PasswordLength) AddDirection(axis); bool actionHeld = In.GetAction(JoystickSide.Both, InteractMode.Continious); if (actionHeld == false) _actionReleased = true; bool canConfirm = _enteredCombination.Length == PasswordLength && _actionReleased; _confirmHoldTime = canConfirm && actionHeld ? Mathf.Min(_confirmHoldTime + Time.unscaledDeltaTime, _confirmHoldDuration) : 0f; SetConfirmProgress(_confirmHoldTime / _confirmHoldDuration); if (_confirmHoldTime < _confirmHoldDuration) return; _confirmHoldTime = 0f; _actionReleased = false; SetConfirmProgress(0f); if (_stage == PanelStage.VerifyPassword) VerifyPassword(); else SetStage(PanelStage.ConfirmPassword); } private void UpdateConfirmationControls() { Vector2Int axis = In.GetAxis(JoystickSide.Both, InteractMode.Discrete); int selectionDirection = axis.y != 0 ? -axis.y : axis.x; if (selectionDirection != 0) { _selectedOptionIndex = (_selectedOptionIndex + Math.Sign(selectionDirection) + _confirmationOptions.Length) % _confirmationOptions.Length; UpdateConfirmationSelection(); } bool actionHeld = In.GetAction(JoystickSide.Both, InteractMode.Continious); if (actionHeld == false) _actionReleased = true; if (_actionReleased && In.GetAction(JoystickSide.Both, InteractMode.Discrete)) ExecuteConfirmationOption(); } private void UpdateCancelInput() { bool serviceHeld = In.GetService(); if (serviceHeld == false) _serviceReleased = true; _cancelHoldTime = _serviceReleased && serviceHeld ? Mathf.Min(_cancelHoldTime + Time.unscaledDeltaTime, _cancelHoldDuration) : 0f; if (_cancelHoldTime >= _cancelHoldDuration) Close(false); } private void AddDirection(Vector2Int axis) { char direction = GetDirectionSymbol(axis); if (direction == default) return; _enteredCombination.Append(direction); UpdateCombinationView(); ClearStatus(); } private void VerifyPassword() { if (SettingsPasswordStore.Validate(_enteredCombination.ToString())) { if (_purpose == PanelPurpose.SettingsAccess) Close(true); else SetStage(PanelStage.EnterNewPassword); return; } _enteredCombination.Clear(); UpdateCombinationView(); ShowStatus("НЕВЕРНЫЙ ПАРОЛЬ", _errorColor, 1.5f); } private void ExecuteConfirmationOption() { _actionReleased = false; if (_selectedOptionIndex == SaveOptionIndex) { if (SettingsPasswordStore.Save(_enteredCombination.ToString())) Close(true); else ShowStatus("НЕ УДАЛОСЬ СОХРАНИТЬ ПАРОЛЬ", _errorColor, 2f); return; } if (_selectedOptionIndex == ReenterOptionIndex) { SetStage(PanelStage.EnterNewPassword); return; } if (_selectedOptionIndex == CancelOptionIndex) Close(false); } private void SetStage(PanelStage stage) { _stage = stage; if (stage != PanelStage.ConfirmPassword) _enteredCombination.Clear(); _confirmHoldTime = 0f; _selectedOptionIndex = 0; _actionReleased = false; SetConfirmProgress(0f); ClearStatus(); bool confirmationVisible = stage == PanelStage.ConfirmPassword; _confirmationGroup.alpha = confirmationVisible ? 1f : 0f; _confirmationGroup.blocksRaycasts = confirmationVisible; _combination.gameObject.SetActive(confirmationVisible == false); _confirmProgressFill.parent.gameObject.SetActive(confirmationVisible == false); if (stage == PanelStage.VerifyPassword) { _title.text = _purpose == PanelPurpose.SettingsAccess ? "ВВЕДИТЕ ПАРОЛЬ" : "СТАРЫЙ ПАРОЛЬ"; _hint.text = "Введите 6 движений любым джойстиком\nи удерживайте кнопку действия 1 секунду"; } else if (stage == PanelStage.EnterNewPassword) { _title.text = "НОВЫЙ ПАРОЛЬ"; _hint.text = "Введите 6 движений любым джойстиком\nи удерживайте кнопку действия 1 секунду"; } else { _title.text = "ПОДТВЕРЖДЕНИЕ"; _hint.text = $"Сохранить комбинацию {FormatCombination(_enteredCombination.ToString())}?"; UpdateConfirmationSelection(); } UpdateCombinationView(); } private void UpdateCombinationView() => _combination.text = FormatCombination(_enteredCombination.ToString()); private void UpdateConfirmationSelection() { for (int i = 0; i < _confirmationOptions.Length; i++) { bool selected = i == _selectedOptionIndex; _confirmationOptions[i].color = selected ? _selectedOptionColor : _normalOptionColor; _confirmationOptions[i].rectTransform.localScale = selected ? Vector3.one * 1.08f : Vector3.one; } } private void SetConfirmProgress(float progress) { Vector2 anchorMax = _confirmProgressFill.anchorMax; anchorMax.x = Mathf.Clamp01(progress); _confirmProgressFill.anchorMax = anchorMax; } private void ShowStatus(string message, Color color, float duration) { _status.text = message; _status.color = color; _statusHideTime = Time.unscaledTime + duration; } private void UpdateStatus() { if (_statusHideTime > 0f && Time.unscaledTime >= _statusHideTime) ClearStatus(); } private void ClearStatus() { _status.text = string.Empty; _statusHideTime = 0f; } private void Close(bool accepted) { if (_isClosing) return; _isClosing = true; ControlsEnabled = false; _fadeTween?.Kill(); _fadeTween = _canvasGroup.DOFade(0f, _fadeDuration).SetEase(Ease.InCubic).SetUpdate(true); _fadeTween.OnComplete(() => { _canvasGroup.blocksRaycasts = false; if (accepted) OnAccepted?.Invoke(); else OnCancelled?.Invoke(); }); _fadeTween.PlayForward(); } private static char GetDirectionSymbol(Vector2Int axis) { if (axis.y > 0) return 'U'; if (axis.y < 0) return 'D'; if (axis.x < 0) return 'L'; if (axis.x > 0) return 'R'; return default; } private static string FormatCombination(string combination) { var result = new StringBuilder(); for (int i = 0; i < PasswordLength; i++) { if (i > 0) result.Append(" "); result.Append(i < combination.Length ? GetDirectionArrow(combination[i]) : "·"); } return result.ToString(); } private static string GetDirectionArrow(char direction) { return direction switch { 'U' => "↑", 'D' => "↓", 'L' => "←", 'R' => "→", _ => "·" }; } private enum PanelPurpose { SettingsAccess, PasswordChange } private enum PanelStage { VerifyPassword, EnterNewPassword, ConfirmPassword } }