371 lines
11 KiB
C#
371 lines
11 KiB
C#
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
|
||
}
|
||
}
|