Danro/Assets/Scripts/Settings/UI/TextInputSettingsBinder.cs
Danil Iarosh 78d7679e2b
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat(prizes, ui): complete Mar 24-Apr 2 sprint ahead of schedule
- завершен спринт 24 марта - 2 апреля с опережением плана и закрытием задач раньше срока
- расширена и стабилизирована система призов:
  добавлены и доработаны QR-tier конфиги, новые типы и модели данных,
  сохранение `qrCodes` в `prizes.json`, fallback-логика и синхронизация
  через `PrizeSystemManager`, `PrizeDatabase` и `LevelPrizeConfig`
- переработан flow настройки QR-призов:
  добавлены `QRCodeSettingsModal` и связанный UI-код,
  разнесены короткий текст приза и полный текст сообщения,
  добавлены шаблоны форматирования, корректная подстановка текста
  в QR и в UI-модалках, а также сохранение конфигурации между запусками
- доработан flow суперприза и текстовых настроек:
  добавлены fallback-правила показа, корректная обработка пустых строк
  и строк из одних пробелов, обновлены binders и показ текста в меню
- реализована и обновлена отдельная настройка витрины:
  добавлена `PrizeStandSettingsModal`, обновлена навигация строк,
  логика выбора ячейки и порогов очков, циклический выбор ячеек,
  ускорение изменения значений и исправления для keyboard/gamepad UX
- доработана игровая модалка витрины `PrizeStandModal`:
  добавлен вывод очков игрока, форматируемый текст счета,
  улучшено отображение ячеек и связанная логика выбора приза
- исправлен подсчет очков и интеграция с prize flow:
  `ScoreSystem` переведен на событийное обновление текущего счета,
  добавлены `CurrentScore`, `CurrentScoreChanged` и корректный display score,
  витрина и связанные UI-элементы теперь получают актуальные очки,
  а не нулевые или несвоевременные preview-значения
- переработаны модальные окна и навигация UI:
  улучшены `Modal`, `ModalManager`, `UIRowPointer`, `RulesModal`,
  `QRCodeModal`, `PrizeStandModal`, `LevelPrizeSettingsModal` и др.,
  чтобы фокус, указатель и submit работали консистентно без мыши
- обновлен win/prize UI:
  доработаны `WinBannerController`, `GameEndUIController`,
  `MainMenu`, `QRCodeUIController`, `TopPanelTextController` и настройки UI,
  улучшены тексты баннеров, отображение призов и итогового счета
- обновлены сцены и ассеты под новый flow:
  `MainMenu`, `Level UI`, prefab'ы строк настроек и кнопок,
  глобальные префабы, шрифты TMP, иконки, recovery-данные и ресурсы UI
- добавлены вспомогательные UI-компоненты и служебные скрипты:
  `ButtonTextFocusColor`, `SelectableDescriptionTarget`, `QRTier`
  и другие изменения, поддерживающие новый конфигурируемый prize UI
2026-04-01 12:47:17 +03:00

260 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using RR.Common.Settings;
using UnityEngine;
namespace RR.Common.UI
{
/// <summary>
/// Биндер для связывания кнопки настроек с модальным окном ввода текста.
/// При нажатии кнопки открывает TextInputModal с текущим значением настройки,
/// при подтверждении ввода сохраняет новое значение в настройку.
///
/// ИСПОЛЬЗОВАНИЕ:
/// 1. Создайте в MainSettingsDatabase настройку типа String (например, "prize.description")
/// 2. На объект с SettingsButtonBinder добавьте компонент TextInputSettingsBinder
/// 3. Укажите ссылку на TextInputModal (должен быть в сцене)
/// 4. Настройте параметры:
/// - settingKey: ключ настройки (если пусто, используется ключ кнопки)
/// - modalTitle: заголовок окна ввода
/// - maxLength: максимальная длина текста
/// - customAlphabet: кастомный алфавит (опционально)
/// 5. При нажатии кнопки в настройках автоматически откроется окно ввода
/// 6. При подтверждении ввода значение сохранится в настройку
///
/// ПРИМЕР НАСТРОЙКИ В MainSettingsDatabase:
/// {
/// "key": "prize.phoneNumber",
/// "displayName": "Номер телефона",
/// "description": "Введите номер телефона для связи",
/// "group": "Prizes",
/// "valueType": "String",
/// "defaultString": "",
/// "shownInSettings": true,
/// "exposed": true
/// }
/// </summary>
[RequireComponent(typeof(SettingsButtonBinder))]
public class TextInputSettingsBinder : MonoBehaviour
{
[Header("Text Input Settings")]
[Tooltip("Ключ настройки для сохранения/загрузки значения")]
[SerializeField] private string settingKey;
[Tooltip("Ссылка на модальное окно ввода текста")]
[SerializeField] private TextInputModal textInputModal;
[Header("Modal Settings")]
[Tooltip("Заголовок модального окна")]
[SerializeField] private string modalTitle = "Введите текст";
[Tooltip("Максимальная длина текста (количество ячеек)")]
[SerializeField] private int maxLength = 10;
[Tooltip("Алфавит для ввода (если пусто - использует дефолтный)")]
[SerializeField] private string[] customAlphabet;
private SettingsButtonBinder buttonBinder;
private SettingEntry settingEntry;
private const string BigPrizeSettingKey = "prizes.bigPrize";
private void Awake()
{
buttonBinder = GetComponent<SettingsButtonBinder>();
// Если ключ настройки не задан, используем ключ кнопки
if (string.IsNullOrWhiteSpace(settingKey))
{
settingKey = buttonBinder.Key;
}
Debug.Log($"[TextInputSettingsBinder] settingKey: '{settingKey}'");
// Если TextInputModal не назначен, ищем его на сцене
if (textInputModal == null)
{
textInputModal = FindFirstObjectByType<TextInputModal>();
if (textInputModal == null)
{
Debug.LogError("[TextInputSettingsBinder] TextInputModal not found in scene! Please add TextInputModal to the scene or assign it manually.", this);
}
else
{
Debug.Log($"[TextInputSettingsBinder] Found TextInputModal on scene: {textInputModal.name}");
}
}
}
private void Start()
{
if (string.IsNullOrWhiteSpace(settingKey))
{
Debug.LogError("[TextInputSettingsBinder] Setting key is not set!", this);
return;
}
// Получаем определение настройки
if (SettingsManager.Instance)
{
settingEntry = SettingsManager.Instance.Define(settingKey);
if (settingEntry == null)
{
Debug.LogWarning($"[TextInputSettingsBinder] Setting '{settingKey}' not found in SettingsManager!");
}
}
}
private void OnEnable()
{
if (textInputModal != null)
{
textInputModal.OnTextConfirmed += OnTextConfirmed;
textInputModal.OnTextCancelled += OnTextCancelled;
}
}
private void OnDisable()
{
if (textInputModal != null)
{
textInputModal.OnTextConfirmed -= OnTextConfirmed;
textInputModal.OnTextCancelled -= OnTextCancelled;
}
}
/// <summary>
/// Открывает модальное окно ввода текста с текущим значением настройки
/// </summary>
public void OpenTextInput()
{
if (textInputModal == null)
{
Debug.LogError("[TextInputSettingsBinder] TextInputModal is not assigned!", this);
return;
}
// Загружаем текущее значение из настройки
string currentValue = LoadCurrentValue();
// ВАЖНО: Порядок вызовов:
// 1. Сначала устанавливаем алфавит (если задан)
if (customAlphabet != null && customAlphabet.Length > 0)
{
textInputModal.SetAlphabet(customAlphabet);
}
// 2. Потом устанавливаем количество ячеек (это создаст ячейки с сохраненным текстом)
if (maxLength > 0)
{
textInputModal.SetCellCount(maxLength);
}
// 3. Открываем модалку с текущим значением (ячейки уже созданы, просто обновим значения)
textInputModal.OpenForInput(modalTitle, currentValue);
}
/// <summary>
/// Загружает текущее значение настройки
/// </summary>
private string LoadCurrentValue()
{
if (SettingsManager.Instance == null)
{
Debug.LogWarning("[TextInputSettingsBinder] SettingsManager is not available!");
return "";
}
// Проверяем, существует ли настройка
var entry = SettingsManager.Instance.Define(settingKey);
if (entry == null)
{
Debug.LogWarning($"[TextInputSettingsBinder] Setting '{settingKey}' not found!");
return "";
}
// Пытаемся загрузить значение как строку
try
{
string value = SettingsManager.Instance.GetString(settingKey);
return value ?? string.Empty;
}
catch (System.Exception ex)
{
Debug.LogWarning($"[TextInputSettingsBinder] Failed to load value for '{settingKey}': {ex.Message}");
return string.Empty;
}
}
private string NormalizeConfirmedValue(string text)
{
if (settingKey == BigPrizeSettingKey)
{
return string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim();
}
return text ?? string.Empty;
}
/// <summary>
/// Вызывается при подтверждении ввода текста
/// </summary>
private void OnTextConfirmed(string text)
{
if (SettingsManager.Instance == null)
{
Debug.LogError("[TextInputSettingsBinder] SettingsManager is not available!");
return;
}
text = NormalizeConfirmedValue(text);
// Сохраняем значение в настройку
SettingsManager.Instance.SetString(settingKey, text);
// Автоматически сохраняем настройки на диск
SettingsManager.Instance.SaveSettings();
}
/// <summary>
/// Вызывается при отмене ввода текста
/// </summary>
private void OnTextCancelled()
{
// Ничего не делаем при отмене
}
/// <summary>
/// Устанавливает заголовок модального окна
/// </summary>
public void SetModalTitle(string title)
{
modalTitle = title;
}
/// <summary>
/// Устанавливает максимальную длину текста
/// </summary>
public void SetMaxLength(int length)
{
maxLength = length;
if (textInputModal != null)
{
textInputModal.SetCellCount(length);
}
}
/// <summary>
/// Устанавливает пользовательский алфавит
/// </summary>
public void SetCustomAlphabet(string[] alphabet)
{
customAlphabet = alphabet;
if (textInputModal != null && alphabet != null && alphabet.Length > 0)
{
textInputModal.SetAlphabet(alphabet);
}
}
}
}