feat(task-0005): implement splash delay and loading progress with reactive properties
- Add SplashState delay timer using BootSettings.SplashDurationSeconds with cancellation support - Implement LoadState progress tracking via ReactiveProperty<float> 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<float> со пошаговыми обновлениями - Обновлён LoadingUIViewModel для принятия и экспорта реактивного свойства Progress - Подключён LoadingUIView к изменениям прогресса ViewModel с использованием подписок UniRx - Добавлен CompositeDisposable для правильной очистки UI подписок в Release() - Масштабирование ProgressFill на основе значения прогресса для визуальной обратной связи
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# TASK-0004: UI база и ViewModel слой
|
# TASK-0004: UI база и ViewModel слой
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Ready
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
Создать базовый UI слой, в котором View отвечает только за Unity-ссылки и биндинги, а логика находится во ViewModel или state/flow сервисах.
|
Создать базовый UI слой, в котором View отвечает только за Unity-ссылки и биндинги, а логика находится во ViewModel или state/flow сервисах.
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# TASK-0005: SplashState, LoadState и прогресс
|
# TASK-0005: SplashState, LoadState и прогресс
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Ready
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
Реализовать splash и loading состояния с настоящей отменой async-операций и реактивным прогрессом через UniRx.
|
Реализовать splash и loading состояния с настоящей отменой async-операций и реактивным прогрессом через UniRx.
|
||||||
|
|||||||
@@ -323,7 +323,8 @@ Transform:
|
|||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_Children: []
|
m_Children:
|
||||||
|
- {fileID: 1000000205}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 3
|
m_RootOrder: 3
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
@@ -339,6 +340,37 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: b2222222222222222222222222222222, type: 3}
|
m_Script: {fileID: 11500000, guid: b2222222222222222222222222222222, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
|
<ProgressFill>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
|
--- !u!1 &1000000301
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -1,24 +1,49 @@
|
|||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
|
using QuizPleaseTest.Boot.Settings;
|
||||||
using QuizPleaseTest.Boot.UI;
|
using QuizPleaseTest.Boot.UI;
|
||||||
using QuizPleaseTest.Common.StateMachine;
|
using QuizPleaseTest.Common.StateMachine;
|
||||||
|
using UniRx;
|
||||||
|
|
||||||
namespace QuizPleaseTest.Boot.States
|
namespace QuizPleaseTest.Boot.States
|
||||||
{
|
{
|
||||||
public class LoadState : IState
|
public class LoadState : IState
|
||||||
{
|
{
|
||||||
private readonly LoadingUIView _view;
|
private readonly LoadingUIView _view;
|
||||||
|
private readonly BootSettings _settings;
|
||||||
|
private readonly ReactiveProperty<float> _progress = new ReactiveProperty<float>(0f);
|
||||||
|
|
||||||
public LoadState(LoadingUIView view)
|
public IReadOnlyReactiveProperty<float> Progress => _progress;
|
||||||
|
|
||||||
|
public LoadState(LoadingUIView view, BootSettings settings)
|
||||||
{
|
{
|
||||||
_view = view;
|
_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();
|
_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)
|
public UniTask ExitAsync(CancellationToken ct)
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
|
using QuizPleaseTest.Boot.Settings;
|
||||||
using QuizPleaseTest.Boot.UI;
|
using QuizPleaseTest.Boot.UI;
|
||||||
using QuizPleaseTest.Common.StateMachine;
|
using QuizPleaseTest.Common.StateMachine;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace QuizPleaseTest.Boot.States
|
namespace QuizPleaseTest.Boot.States
|
||||||
{
|
{
|
||||||
public class SplashState : IState
|
public class SplashState : IState
|
||||||
{
|
{
|
||||||
private readonly SplashUIView _view;
|
private readonly SplashUIView _view;
|
||||||
|
private readonly BootSettings _settings;
|
||||||
|
|
||||||
public SplashState(SplashUIView view)
|
public SplashState(SplashUIView view, BootSettings settings)
|
||||||
{
|
{
|
||||||
_view = view;
|
_view = view;
|
||||||
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UniTask EnterAsync(CancellationToken ct)
|
public async UniTask EnterAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
_view.Bind(new SplashUIViewModel());
|
_view.Bind(new SplashUIViewModel());
|
||||||
_view.Initialize();
|
_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)
|
public UniTask ExitAsync(CancellationToken ct)
|
||||||
|
|||||||
@@ -1,8 +1,45 @@
|
|||||||
using QuizPleaseTest.Common.UI;
|
using QuizPleaseTest.Common.UI;
|
||||||
|
using UniRx;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace QuizPleaseTest.Boot.UI
|
namespace QuizPleaseTest.Boot.UI
|
||||||
{
|
{
|
||||||
public class LoadingUIView : UIView<LoadingUIViewModel>
|
public class LoadingUIView : UIView<LoadingUIViewModel>
|
||||||
{
|
{
|
||||||
|
[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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
using System;
|
||||||
using QuizPleaseTest.Common.UI;
|
using QuizPleaseTest.Common.UI;
|
||||||
|
using UniRx;
|
||||||
|
|
||||||
namespace QuizPleaseTest.Boot.UI
|
namespace QuizPleaseTest.Boot.UI
|
||||||
{
|
{
|
||||||
public class LoadingUIViewModel : IUIViewModel
|
public class LoadingUIViewModel : IUIViewModel
|
||||||
{
|
{
|
||||||
|
public IReadOnlyReactiveProperty<float> Progress { get; }
|
||||||
|
|
||||||
|
public LoadingUIViewModel(IReadOnlyReactiveProperty<float> progress)
|
||||||
|
{
|
||||||
|
Progress = progress ?? throw new ArgumentNullException(nameof(progress));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user