From 6601c8ea2265fbbea433cee665073cc58513dca1 Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Wed, 27 May 2026 04:35:05 +0700 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfeat(task-0006):=20implement=20menu=20?= =?UTF-8?q?restart=20button=20and=20signal=20coordination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update MenuUIViewModel with IMenuRestartSignal dependency and Restart() method - Add RestartButton to MenuUIView with listener management in Initialize/Release - Connect MenuUIView click handler to ViewModel.Restart() callback - Fix race condition in MenuRestartSignal.RequestRestart() by nulling completion source first - Wrap MenuState.WaitAsync() in try-catch for proper view cleanup on cancellation - Update TASK-0006 status to Ready Выполнена задача TASK-0006: реализована кнопка Restart и координация сигналов - Обновлён MenuUIViewModel с зависимостью IMenuRestartSignal и методом Restart() - Добавлена кнопка RestartButton в MenuUIView с управлением слушателями в Initialize/Release - Подключен обработчик кликов MenuUIView к колбэку ViewModel.Restart() - Исправлено состояние гонки в MenuRestartSignal.RequestRestart() путем обнуления completion source - Обёрнут WaitAsync в MenuState в try-catch для корректной очистки view при отмене - Обновлён статус TASK-0006 до Ready --- Agent/Task/TASK-0006.md | 4 ++ Assets/Scenes/SampleScene.unity | 46 +++++++++++++++++++ Assets/Scripts/Boot/Flow/MenuRestartSignal.cs | 4 +- Assets/Scripts/Boot/States/MenuState.cs | 14 +++++- Assets/Scripts/Boot/UI/MenuUIView.cs | 31 +++++++++++++ Assets/Scripts/Boot/UI/MenuUIViewModel.cs | 13 ++++++ 6 files changed, 109 insertions(+), 3 deletions(-) diff --git a/Agent/Task/TASK-0006.md b/Agent/Task/TASK-0006.md index bc8697f..36b5eae 100644 --- a/Agent/Task/TASK-0006.md +++ b/Agent/Task/TASK-0006.md @@ -1,5 +1,9 @@ # TASK-0006: MenuState и Restart +## Статус + +Ready + ## Цель Реализовать меню с кнопкой `Restart`, которое завершает `MenuState` и возвращает flow к загрузке. diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 7e6ee56..04d52cf 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -381,6 +381,7 @@ GameObject: m_Component: - component: {fileID: 1000000302} - component: {fileID: 1000000303} + - component: {fileID: 1000000304} m_Layer: 0 m_Name: MenuView m_TagString: Untagged @@ -414,3 +415,48 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b3333333333333333333333333333333, type: 3} m_Name: m_EditorClassIdentifier: + k__BackingField: {fileID: 1000000304} +--- !u!114 &1000000304 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1000000301} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.88235295, g: 0.88235295, b: 0.88235295, a: 1} + m_PressedColor: {r: 0.69803923, g: 0.69803923, b: 0.69803923, a: 1} + m_SelectedColor: {r: 0.88235295, g: 0.88235295, b: 0.88235295, a: 1} + m_DisabledColor: {r: 0.52156866, g: 0.52156866, b: 0.52156866, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: [] diff --git a/Assets/Scripts/Boot/Flow/MenuRestartSignal.cs b/Assets/Scripts/Boot/Flow/MenuRestartSignal.cs index f6194af..a23173c 100644 --- a/Assets/Scripts/Boot/Flow/MenuRestartSignal.cs +++ b/Assets/Scripts/Boot/Flow/MenuRestartSignal.cs @@ -17,7 +17,9 @@ namespace QuizPleaseTest.Boot.Flow public void RequestRestart() { - _restartCompletionSource?.TrySetResult(); + UniTaskCompletionSource restartCompletionSource = _restartCompletionSource; + _restartCompletionSource = null; + restartCompletionSource?.TrySetResult(); } } } diff --git a/Assets/Scripts/Boot/States/MenuState.cs b/Assets/Scripts/Boot/States/MenuState.cs index 509b985..5e1f920 100644 --- a/Assets/Scripts/Boot/States/MenuState.cs +++ b/Assets/Scripts/Boot/States/MenuState.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using Cysharp.Threading.Tasks; using QuizPleaseTest.Boot.Flow; @@ -19,9 +20,18 @@ namespace QuizPleaseTest.Boot.States public async UniTask EnterAsync(CancellationToken ct) { - _view.Bind(new MenuUIViewModel()); + _view.Bind(new MenuUIViewModel(_restartSignal)); _view.Initialize(); - await _restartSignal.WaitAsync(ct); + + try + { + await _restartSignal.WaitAsync(ct); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + _view.Release(); + throw; + } } public UniTask ExitAsync(CancellationToken ct) diff --git a/Assets/Scripts/Boot/UI/MenuUIView.cs b/Assets/Scripts/Boot/UI/MenuUIView.cs index 530bf4a..8e85ed6 100644 --- a/Assets/Scripts/Boot/UI/MenuUIView.cs +++ b/Assets/Scripts/Boot/UI/MenuUIView.cs @@ -1,8 +1,39 @@ using QuizPleaseTest.Common.UI; +using UnityEngine; +using UnityEngine.UI; namespace QuizPleaseTest.Boot.UI { public class MenuUIView : UIView { + [field: SerializeField] public Button RestartButton { get; private set; } + + public override void Initialize() + { + base.Initialize(); + + if (RestartButton == null) + { + return; + } + + RestartButton.onClick.RemoveListener(OnRestartClicked); + RestartButton.onClick.AddListener(OnRestartClicked); + } + + public override void Release() + { + if (RestartButton != null) + { + RestartButton.onClick.RemoveListener(OnRestartClicked); + } + + base.Release(); + } + + private void OnRestartClicked() + { + ViewModel.Restart(); + } } } diff --git a/Assets/Scripts/Boot/UI/MenuUIViewModel.cs b/Assets/Scripts/Boot/UI/MenuUIViewModel.cs index 1bd8eb7..71bcf7e 100644 --- a/Assets/Scripts/Boot/UI/MenuUIViewModel.cs +++ b/Assets/Scripts/Boot/UI/MenuUIViewModel.cs @@ -1,8 +1,21 @@ +using System; +using QuizPleaseTest.Boot.Flow; using QuizPleaseTest.Common.UI; namespace QuizPleaseTest.Boot.UI { public class MenuUIViewModel : IUIViewModel { + private readonly IMenuRestartSignal _restartSignal; + + public MenuUIViewModel(IMenuRestartSignal restartSignal) + { + _restartSignal = restartSignal ?? throw new ArgumentNullException(nameof(restartSignal)); + } + + public void Restart() + { + _restartSignal.RequestRestart(); + } } }