From fda094dd44a378460cc1749eaa9c8e32751c5dcc Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Wed, 27 May 2026 04:27:33 +0700 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfeat(task-0005):=20implement=20splash?= =?UTF-8?q?=20delay=20and=20loading=20progress=20with=20reactive=20propert?= =?UTF-8?q?ies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add SplashState delay timer using BootSettings.SplashDurationSeconds with cancellation support - Implement LoadState progress tracking via ReactiveProperty with step-by-step updates - Update LoadingUIViewModel to accept and expose Progress reactive property - Connect LoadingUIView to ViewModel progress changes using UniRx subscriptions - Add CompositeDisposable for proper cleanup of UI subscriptions in Release() - Scale ProgressFill transform based on progress value for visual feedback Выполнена задача TASK-0005 и реализованы splash и loading состояния: - Добавлен таймер задержки в SplashState с использованием BootSettings.SplashDurationSeconds и поддержкой отмены - Реализован трекинг прогресса в LoadState через ReactiveProperty со пошаговыми обновлениями - Обновлён LoadingUIViewModel для принятия и экспорта реактивного свойства Progress - Подключён LoadingUIView к изменениям прогресса ViewModel с использованием подписок UniRx - Добавлен CompositeDisposable для правильной очистки UI подписок в Release() - Масштабирование ProgressFill на основе значения прогресса для визуальной обратной связи --- Agent/Task/TASK-0004.md | 4 +++ Agent/Task/TASK-0005.md | 4 +++ Assets/Scenes/SampleScene.unity | 34 +++++++++++++++++- Assets/Scripts/Boot/States/LoadState.cs | 33 ++++++++++++++--- Assets/Scripts/Boot/States/SplashState.cs | 21 +++++++++-- Assets/Scripts/Boot/UI/LoadingUIView.cs | 37 ++++++++++++++++++++ Assets/Scripts/Boot/UI/LoadingUIViewModel.cs | 8 +++++ 7 files changed, 133 insertions(+), 8 deletions(-) diff --git a/Agent/Task/TASK-0004.md b/Agent/Task/TASK-0004.md index 87b7b90..897f4f6 100644 --- a/Agent/Task/TASK-0004.md +++ b/Agent/Task/TASK-0004.md @@ -1,5 +1,9 @@ # TASK-0004: UI база и ViewModel слой +## Статус + +Ready + ## Цель Создать базовый UI слой, в котором View отвечает только за Unity-ссылки и биндинги, а логика находится во ViewModel или state/flow сервисах. diff --git a/Agent/Task/TASK-0005.md b/Agent/Task/TASK-0005.md index d6d41c7..21c8859 100644 --- a/Agent/Task/TASK-0005.md +++ b/Agent/Task/TASK-0005.md @@ -1,5 +1,9 @@ # TASK-0005: SplashState, LoadState и прогресс +## Статус + +Ready + ## Цель Реализовать splash и loading состояния с настоящей отменой async-операций и реактивным прогрессом через UniRx. diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index fd94fe4..7e6ee56 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -323,7 +323,8 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] + m_Children: + - {fileID: 1000000205} m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -339,6 +340,37 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b2222222222222222222222222222222, type: 3} m_Name: m_EditorClassIdentifier: + k__BackingField: {fileID: 1000000205} +--- !u!1 &1000000204 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1000000205} + m_Layer: 0 + m_Name: LoadingProgressFill + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1000000205 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1000000204} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1000000202} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1000000301 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Boot/States/LoadState.cs b/Assets/Scripts/Boot/States/LoadState.cs index a259926..26e53be 100644 --- a/Assets/Scripts/Boot/States/LoadState.cs +++ b/Assets/Scripts/Boot/States/LoadState.cs @@ -1,24 +1,49 @@ +using System; using System.Threading; using Cysharp.Threading.Tasks; +using QuizPleaseTest.Boot.Settings; using QuizPleaseTest.Boot.UI; using QuizPleaseTest.Common.StateMachine; +using UniRx; namespace QuizPleaseTest.Boot.States { public class LoadState : IState { private readonly LoadingUIView _view; + private readonly BootSettings _settings; + private readonly ReactiveProperty _progress = new ReactiveProperty(0f); - public LoadState(LoadingUIView view) + public IReadOnlyReactiveProperty Progress => _progress; + + public LoadState(LoadingUIView view, BootSettings settings) { _view = view; + _settings = settings; } - public UniTask EnterAsync(CancellationToken ct) + public async UniTask EnterAsync(CancellationToken ct) { - _view.Bind(new LoadingUIViewModel()); + _progress.Value = 0f; + _view.Bind(new LoadingUIViewModel(Progress)); _view.Initialize(); - return UniTask.CompletedTask; + + int loadSteps = _settings.LoadSteps > 0 ? _settings.LoadSteps : 1; + int stepDurationMs = _settings.LoadStepDurationMs > 0 ? _settings.LoadStepDurationMs : 0; + + try + { + for (int step = 1; step <= loadSteps; step++) + { + await UniTask.Delay(stepDurationMs, cancellationToken: ct); + _progress.Value = (float)step / loadSteps; + } + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + _view.Release(); + throw; + } } public UniTask ExitAsync(CancellationToken ct) diff --git a/Assets/Scripts/Boot/States/SplashState.cs b/Assets/Scripts/Boot/States/SplashState.cs index dcf20e0..d6d3833 100644 --- a/Assets/Scripts/Boot/States/SplashState.cs +++ b/Assets/Scripts/Boot/States/SplashState.cs @@ -1,24 +1,39 @@ +using System; using System.Threading; using Cysharp.Threading.Tasks; +using QuizPleaseTest.Boot.Settings; using QuizPleaseTest.Boot.UI; using QuizPleaseTest.Common.StateMachine; +using UnityEngine; namespace QuizPleaseTest.Boot.States { public class SplashState : IState { private readonly SplashUIView _view; + private readonly BootSettings _settings; - public SplashState(SplashUIView view) + public SplashState(SplashUIView view, BootSettings settings) { _view = view; + _settings = settings; } - public UniTask EnterAsync(CancellationToken ct) + public async UniTask EnterAsync(CancellationToken ct) { _view.Bind(new SplashUIViewModel()); _view.Initialize(); - return UniTask.CompletedTask; + + int delayMs = Mathf.Max(0, Mathf.RoundToInt(_settings.SplashDurationSeconds * 1000f)); + try + { + await UniTask.Delay(delayMs, cancellationToken: ct); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + _view.Release(); + throw; + } } public UniTask ExitAsync(CancellationToken ct) diff --git a/Assets/Scripts/Boot/UI/LoadingUIView.cs b/Assets/Scripts/Boot/UI/LoadingUIView.cs index 1395a3e..9fcb19b 100644 --- a/Assets/Scripts/Boot/UI/LoadingUIView.cs +++ b/Assets/Scripts/Boot/UI/LoadingUIView.cs @@ -1,8 +1,45 @@ using QuizPleaseTest.Common.UI; +using UniRx; +using UnityEngine; namespace QuizPleaseTest.Boot.UI { public class LoadingUIView : UIView { + [field: SerializeField] public Transform ProgressFill { get; private set; } + + private CompositeDisposable _disposables; + + public override void Initialize() + { + base.Initialize(); + + _disposables?.Dispose(); + _disposables = new CompositeDisposable(); + + SetProgress(ViewModel.Progress.Value); + ViewModel.Progress + .Subscribe(SetProgress) + .AddTo(_disposables); + } + + public override void Release() + { + _disposables?.Dispose(); + _disposables = null; + base.Release(); + } + + private void SetProgress(float progress) + { + if (ProgressFill == null) + { + return; + } + + Vector3 scale = ProgressFill.localScale; + scale.x = Mathf.Clamp01(progress); + ProgressFill.localScale = scale; + } } } diff --git a/Assets/Scripts/Boot/UI/LoadingUIViewModel.cs b/Assets/Scripts/Boot/UI/LoadingUIViewModel.cs index 4a9d72e..7753074 100644 --- a/Assets/Scripts/Boot/UI/LoadingUIViewModel.cs +++ b/Assets/Scripts/Boot/UI/LoadingUIViewModel.cs @@ -1,8 +1,16 @@ +using System; using QuizPleaseTest.Common.UI; +using UniRx; namespace QuizPleaseTest.Boot.UI { public class LoadingUIViewModel : IUIViewModel { + public IReadOnlyReactiveProperty Progress { get; } + + public LoadingUIViewModel(IReadOnlyReactiveProperty progress) + { + Progress = progress ?? throw new ArgumentNullException(nameof(progress)); + } } }