Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- завершен спринт 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
260 lines
10 KiB
C#
260 lines
10 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|