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)); + } } }