Compare commits

...

29 Commits

Author SHA1 Message Date
378b00096a Merge pull request 'work_branch' (#13) from work_branch into main
Reviewed-on: #13
2026-05-29 10:40:47 +02:00
Mausham
6d9b096e8e added anim 2026-05-29 14:24:03 +05:45
Mausham
919169157e added some features in artbook 2026-05-29 12:47:21 +05:45
96e69c3d1a Merge pull request 'work_branch' (#12) from work_branch into main
Reviewed-on: #12
2026-05-29 06:52:37 +02:00
Mausham
a51a73efdd fixess 2026-05-29 10:37:07 +05:45
Mausham
3914adaa84 Merge remote-tracking branch 'origin/main' into work_branch
# Conflicts:
#	Assets/Darkmatter/Code/Features/ColorbookFlow/Features.Colorbook.asmdef
#	Assets/Darkmatter/Code/Features/ColorbookFlow/System/ColorbookFlowController.cs
2026-05-29 10:35:04 +05:45
Mausham
4e8f2ae7ad fixes 2026-05-29 10:31:14 +05:45
bdac5b485f Merge pull request 'Game flow and coloring done' (#11) from savya into main
Reviewed-on: #11
2026-05-29 06:45:39 +02:00
Savya Bikram Shah
b3c096a150 Game flow and coloring done 2026-05-28 19:45:15 +05:45
Mausham
9899a8b549 code added for Artbook 2026-05-28 18:48:08 +05:45
Mausham
820f614415 some additional fixes 2026-05-28 18:32:14 +05:45
92aa1d2751 Merge pull request 'Refactored and Animaiton for HOlder' (#10) from savya into main
Reviewed-on: #10
2026-05-28 14:02:15 +02:00
Savya Bikram Shah
9ac752b23c Refactored and Animaiton for HOlder 2026-05-28 17:45:58 +05:45
252c1b8a41 Merge pull request 'work_branch' (#9) from work_branch into main
Reviewed-on: #9
2026-05-28 13:24:44 +02:00
Mausham
f9a2532495 Fixes 2026-05-28 17:09:12 +05:45
Mausham
e6b6683feb Merge remote-tracking branch 'origin/main' into work_branch 2026-05-28 16:54:28 +05:45
b16f535499 Merge pull request 'savya' (#8) from savya into main
Reviewed-on: #8
2026-05-28 13:07:38 +02:00
Mausham
9ad3c34099 rearranged UI 2026-05-28 16:52:36 +05:45
Savya Bikram Shah
09ad3469f2 Loading finalized 2026-05-28 16:52:06 +05:45
Savya Bikram Shah
98fbad9233 Merge remote-tracking branch 'origin/work_branch' into savya
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-Bold SDF.asset
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-Medium SDF.asset
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-SemiBold SDF.asset
2026-05-28 16:10:23 +05:45
380e8b63bd Merge pull request 'work_branch' (#7) from work_branch into main
Reviewed-on: #7
2026-05-28 12:24:55 +02:00
Mausham
3f14d0b346 completed intro video and mainmenu scene 2026-05-28 15:37:33 +05:45
Mausham
86bf52ced4 Merge remote-tracking branch 'origin/main' into work_branch
# Conflicts:
#	Assets/Darkmatter/Code/Features/MainMenu/Mascot/MainmenuPresenter.cs
#	Assets/Darkmatter/Code/Features/MainMenu/Mascot/MainmenuPresenter.cs.meta
#	Assets/Darkmatter/Code/Features/MainMenu/Mascot/MainmenuView.cs
#	Assets/Darkmatter/Code/Features/MainMenu/Mascot/MainmenuView.cs.meta
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainMenuPresenter.cs
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainMenuPresenter.cs.meta
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainMenuView.cs
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainMenuView.cs.meta
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainmenuPresenter.cs
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainmenuPresenter.cs.meta
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainmenuView.cs
#	Assets/Darkmatter/Code/Features/MainMenu/UI/MainmenuView.cs.meta
2026-05-28 15:11:46 +05:45
Mausham
4064df19bc Merge remote-tracking branch 'origin/main' into work_branch 2026-05-28 15:06:05 +05:45
Mausham
01ec3bec54 fixes 2026-05-28 15:05:57 +05:45
e96fefa642 Merge pull request 'savya' (#6) from savya into main
Reviewed-on: #6
2026-05-28 11:20:23 +02:00
Mausham
f1f3a35c6d Merge remote-tracking branch 'origin/main' into work_branch 2026-05-28 13:39:01 +05:45
Mausham
84fea79158 commit 2026-05-28 13:38:23 +05:45
Mausham
e5b63e158c added gui in color book scene 2026-05-28 12:35:02 +05:45
467 changed files with 98831 additions and 29532 deletions

View File

@@ -20,6 +20,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 145ae55f6571bfe4fbadaefb863ba69d
m_Address: Colorbook
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e5f73f24e812e4a98b4c17d533fd3d6d
m_Address: Gameplay
m_ReadOnly: 0

View File

@@ -0,0 +1,5 @@
using Darkmatter.App.LifetimeScopes;
public class ColorBookLifetimeScope : BaseLifetimeScope
{
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8bbdb0f5cdf25c34086f816c59836c9d

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using UnityEngine;
namespace Darkmatter.Core.Contracts.Features.Coloring;
public interface IColoringController
{
UniTask InitializeRegionsAsync(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors,
CancellationToken ct);
void PaintRegion(string regionId, Color color);
IReadOnlyDictionary<string, Color> GetCurrentColors();
void Clear();
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5ba5a7108234751ac286a57795fac06
timeCreated: 1779971202

View File

@@ -10,8 +10,10 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
string Id { get; }
string DisplayName { get; }
Sprite DefaultThumbnail { get; }
GameObject Prefab { get; }
GameObject DrawingPrefab { get; }
GameObject ColoringPrefab { get; }
IReadOnlyList<ShapeSO> Pieces { get; }
IReadOnlyList<ColorRegionDTO> Regions { get; }
string ColorPaletteId { get; }
}
}

View File

@@ -0,0 +1,12 @@
using Cysharp.Threading.Tasks;
namespace Darkmatter.Core.Contracts.Features.GameplayFlow
{
public interface IGameplayFlowController
{
UniTask BackAsync();
UniTask SaveAsync();
UniTask NextAsync();
void OnApplicationPaused();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fee5907f5643d492ab8ba177f84c30f6

View File

@@ -5,9 +5,4 @@ namespace Darkmatter.Core.Contracts.Features.GameplayFlow;
public interface IGameplaySceneRefs
{
RectTransform PaperRoot { get; }
RectTransform SlotsParent { get; }
RectTransform PiecesParent { get; }
RectTransform RegionsParent { get; }
RectTransform HudRoot { get; }
RectTransform TrayPanel { get; }
}

View File

@@ -7,9 +7,11 @@ namespace Darkmatter.Core.Contracts.Features.History
event Action OnStackChanged;
bool CanUndo { get; }
bool CanRedo { get; }
void Push(ICommand cmd); // executes + appends
void Push(ICommand cmd);
void Append(ICommand cmd);
void Undo();
void Redo();
void Clear();
void Clear(); // reverts every command via Undo, then drops (user-facing "wipe" semantics)
void Drop(); // drops the list without reverting (controller cleanup — avoids touching destroyed views)
}
}

View File

@@ -12,6 +12,9 @@ namespace Darkmatter.Core.Contracts.Features.Progression
IReadOnlyCollection<string> CompletedTemplateIds { get; }
void MarkCompleted(string templateId);
string LastOpenedTemplateId { get; }
UniTask SetLastOpenedAsync(string templateId);
DrawingProgress? GetProgress(string templateId);
UniTask SaveProgressAsync(DrawingProgress progress);
UniTask SaveProgressAsync(DrawingProgress progress, byte[] thumbnailPng);

View File

@@ -1,18 +1,19 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Core.Data.Dynamic.Features.Coloring
{
public readonly struct ColorRegionDTO
{
public string RegionId { get; }
public Sprite Sprite { get; }
public string Id { get; }
public Image Image { get; }
public Vector2 AnchoredPosition { get; }
public Color InitialColor { get; }
public ColorRegionDTO(string regionId, Sprite sprite, Vector2 anchoredPosition, Color initialColor)
public ColorRegionDTO(string id, Image image, Vector2 anchoredPosition, Color initialColor)
{
RegionId = regionId;
Sprite = sprite;
Id = id;
Image = image;
AnchoredPosition = anchoredPosition;
InitialColor = initialColor;
}

View File

@@ -7,5 +7,6 @@ namespace Darkmatter.Core.Data.Dynamic.Features.Progression
public struct ProgressionRootDto
{
public List<DrawingProgress> records;
public string lastOpenedTemplateId;
}
}

View File

@@ -0,0 +1,6 @@
using UnityEngine;
namespace Darkmatter.Core
{
public record struct OpenArtBookSignal;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf8582ced90151445a9fa7b610f59ce9

View File

@@ -0,0 +1,6 @@
using UnityEngine;
namespace Darkmatter.Core
{
public record struct OpenColorBookSignal;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73bddc8d591de2a438044c2a9ee180be

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace Darkmatter.Core
{
public record struct ReturnToMainMenuSignal;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fdd1f5bd7186d6244b2311e9f5b18a95

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4c2af4ea812e45de8f189ce182b2a22e
timeCreated: 1779963993

View File

@@ -0,0 +1,3 @@
namespace Darkmatter.Core.Data.Signals.Features.MainMenu;
public record struct PlayBtnClickedSignal();

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3dfe3ef5d7da4861becbc8791ce7c852
timeCreated: 1779964014

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.ShapeBuilder
{
public record struct ShapeBuilderStartedSignal(string TemplateId);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 40f2733e19ad34fe5ba5d63aa2e9a39b

View File

@@ -12,8 +12,10 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
[field: SerializeField] public string Id { get; private set; }
[field: SerializeField] public string DisplayName { get; private set; }
[field: SerializeField] public Sprite DefaultThumbnail { get; private set; }
[field: SerializeField] public GameObject Prefab { get; private set; }
[field: SerializeField] public GameObject DrawingPrefab { get; private set; }
[field: SerializeField] public GameObject ColoringPrefab { get; private set; }
[field: SerializeField] public IReadOnlyList<ShapeSO> Pieces { get; private set; }
[field: SerializeField] public IReadOnlyList<ColorRegionDTO> Regions { get; private set; }
[field: SerializeField] public string ColorPaletteId { get; private set; } = "defaultPalette";
}
}

View File

@@ -8,7 +8,6 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
{
[Header("Radii (canvas units; reference resolution 2048x2048)")]
[SerializeField] private float snapRadius = 100f;
[SerializeField] private float snapGraceMultiplier = 1.5f;
[SerializeField] private float previewRadius = 200f;
[Header("Tween durations (seconds)")]

View File

@@ -6,5 +6,6 @@ namespace Darkmatter.Core.Enums.Services.Audio
ShapeHover = 100,
ShapeSnap = 101,
ShapeReturn = 102,
UiTap = 200,
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5d8eacd004e3e0649b6ff4a6b58c952c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
{
"name": "Features.Artbook",
"rootNamespace": "Darkmatter.Features.Artbook",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dd1597db9436d6f4da32f1391b905651
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af74b0a63b3e4a746885f0e4b911cd91
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Artbook
{
public class ArtBookFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private ArtbookView artbookView;
public void Register(IContainerBuilder builder)
{
if (artbookView != null) builder.RegisterEntryPoint<ArtbookPresenter>().WithParameter(artbookView);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e42bea53c6fa23c45a7c85e9b025bed7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a13b92fe0335e04c8de0c5cbc54f6fb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
using System;
using UnityEngine;
namespace Darkmatter.Features.Artbook
{
public record struct ArtbookEntry(string Id, string Name, Texture2D Thumbnail, DateTime UpdatedUtc);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6821df5d7eb2fe345aeeebd1290d84b3

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Services.Gallery;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Libs.Observer;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Features.Artbook
{
public class ArtbookPresenter : IStartable, IDisposable
{
private const int EntriesPerSpread = 2;
private readonly ArtbookView _view;
private readonly IEventBus _eventBus;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IGalleryService _gallery;
private readonly List<ArtbookEntry> _entries = new();
private readonly List<Sprite> _ownedSprites = new();
private readonly List<Texture2D> _ownedTextures = new();
private CancellationTokenSource _cts;
private IDisposable _openSubscription;
private int _currentSpread;
public ArtbookPresenter(
ArtbookView view,
IEventBus eventBus,
IProgressionSystem progression,
IDrawingTemplateCatalog catalog,
IGalleryService gallery)
{
_view = view;
_eventBus = eventBus;
_progression = progression;
_catalog = catalog;
_gallery = gallery;
}
public void Start()
{
_view.OnBackButtonClicked += HandleBackButtonClicked;
_view.OnColorbookButtonClicked += HandleColorbookButtonClicked;
_view.OnPrevSpreadClicked += HandlePrevSpreadClicked;
_view.OnNextSpreadClicked += HandleNextSpreadClicked;
_view.OnLeftSaveClicked += HandleLeftSaveClicked;
_view.OnRightSaveClicked += HandleRightSaveClicked;
_view.OnLeftEditClicked += HandleLeftEditClicked;
_view.OnRightEditClicked += HandleRightEditClicked;
_openSubscription = _eventBus.Subscribe<OpenArtBookSignal>(HandleOpenArtbookEvent);
}
private void HandleOpenArtbookEvent(OpenArtBookSignal signal)
{
_view.Show();
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
LoadAndShowAsync(_cts.Token).Forget();
}
private async UniTaskVoid LoadAndShowAsync(CancellationToken ct)
{
ClearOwnedAssets();
_entries.Clear();
_currentSpread = 0;
await _catalog.FetchAsync();
if (ct.IsCancellationRequested) return;
foreach (var id in _catalog.AllTemplateIds)
{
if (ct.IsCancellationRequested) return;
var progress = _progression.GetProgress(id);
if (progress is not { hasThumbnail: true }) continue;
var texture = await _progression.GetCachedThumbnailAsync(id);
if (ct.IsCancellationRequested) return;
if (texture == null) continue;
var template = await _catalog.LoadAsync(id);
if (ct.IsCancellationRequested) return;
_ownedTextures.Add(texture);
_entries.Add(new ArtbookEntry(id, template.DisplayName, texture, progress.Value.UpdatedUtc));
}
_entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc));
RenderSpread();
}
private void HandlePrevSpreadClicked()
{
if (_currentSpread <= 0) return;
_currentSpread--;
RenderSpread();
}
private void HandleNextSpreadClicked()
{
if (_currentSpread >= TotalSpreads - 1) return;
_currentSpread++;
RenderSpread();
}
private int TotalSpreads => Mathf.Max(1, Mathf.CeilToInt(_entries.Count / (float)EntriesPerSpread));
private void RenderSpread()
{
_view.SetSpread(GetLeftEntry(), SpriteFor(GetLeftEntry()), GetRightEntry(), SpriteFor(GetRightEntry()));
_view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1);
}
private ArtbookEntry? GetLeftEntry()
{
var idx = _currentSpread * EntriesPerSpread;
return idx < _entries.Count ? _entries[idx] : null;
}
private ArtbookEntry? GetRightEntry()
{
var idx = _currentSpread * EntriesPerSpread + 1;
return idx < _entries.Count ? _entries[idx] : null;
}
private Sprite SpriteFor(ArtbookEntry? entry)
{
if (!entry.HasValue) return null;
var tex = entry.Value.Thumbnail;
if (tex == null) return null;
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
_ownedSprites.Add(sprite);
return sprite;
}
private void HandleLeftSaveClicked() => SaveToGalleryAsync(GetLeftEntry()).Forget();
private void HandleRightSaveClicked() => SaveToGalleryAsync(GetRightEntry()).Forget();
private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry)
{
if (!entry.HasValue || entry.Value.Thumbnail == null) return;
var ct = _cts?.Token ?? CancellationToken.None;
await _gallery.SaveImageAsync(entry.Value.Thumbnail, entry.Value.Id, ct);
}
private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry());
private void HandleRightEditClicked() => OpenForEdit(GetRightEntry());
private void OpenForEdit(ArtbookEntry? entry)
{
if (!entry.HasValue) return;
_eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id));
_view.Hide();
}
private void HandleBackButtonClicked()
{
_eventBus.Publish(new OpenColorBookSignal());
_view.Hide();
}
private void HandleColorbookButtonClicked()
{
_eventBus.Publish(new OpenColorBookSignal());
_view.Hide();
}
private void ClearOwnedAssets()
{
foreach (var s in _ownedSprites) UnityEngine.Object.Destroy(s);
foreach (var t in _ownedTextures) UnityEngine.Object.Destroy(t);
_ownedSprites.Clear();
_ownedTextures.Clear();
}
public void Dispose()
{
_view.OnBackButtonClicked -= HandleBackButtonClicked;
_view.OnColorbookButtonClicked -= HandleColorbookButtonClicked;
_view.OnPrevSpreadClicked -= HandlePrevSpreadClicked;
_view.OnNextSpreadClicked -= HandleNextSpreadClicked;
_view.OnLeftSaveClicked -= HandleLeftSaveClicked;
_view.OnRightSaveClicked -= HandleRightSaveClicked;
_view.OnLeftEditClicked -= HandleLeftEditClicked;
_view.OnRightEditClicked -= HandleRightEditClicked;
_openSubscription?.Dispose();
_cts?.Cancel();
_cts?.Dispose();
ClearOwnedAssets();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ce45172769c70e044a56095bc9da281a

View File

@@ -0,0 +1,92 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Artbook
{
public class ArtbookView : MonoBehaviour
{
[SerializeField] private Button backBtn;
[SerializeField] private Button colorbookBtn;
[Header("Pages")]
[SerializeField] private Image leftPageImage;
[SerializeField] private Image rightPageImage;
[SerializeField] private TMP_Text leftPageNameText;
[SerializeField] private TMP_Text rightPageNameText;
[Header("Page Actions")]
[SerializeField] private Button leftSaveBtn;
[SerializeField] private Button rightSaveBtn;
[SerializeField] private Button leftEditBtn;
[SerializeField] private Button rightEditBtn;
[Header("Navigation")]
[SerializeField] private Button leftArrowBtn;
[SerializeField] private Button rightArrowBtn;
public event Action OnBackButtonClicked;
public event Action OnColorbookButtonClicked;
public event Action OnPrevSpreadClicked;
public event Action OnNextSpreadClicked;
public event Action OnLeftSaveClicked;
public event Action OnRightSaveClicked;
public event Action OnLeftEditClicked;
public event Action OnRightEditClicked;
void Start()
{
backBtn.onClick.AddListener(() => OnBackButtonClicked?.Invoke());
colorbookBtn.onClick.AddListener(() => OnColorbookButtonClicked?.Invoke());
leftArrowBtn.onClick.AddListener(() => OnPrevSpreadClicked?.Invoke());
rightArrowBtn.onClick.AddListener(() => OnNextSpreadClicked?.Invoke());
leftSaveBtn.onClick.AddListener(() => OnLeftSaveClicked?.Invoke());
rightSaveBtn.onClick.AddListener(() => OnRightSaveClicked?.Invoke());
leftEditBtn.onClick.AddListener(() => OnLeftEditClicked?.Invoke());
rightEditBtn.onClick.AddListener(() => OnRightEditClicked?.Invoke());
}
public void Show()
{
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
public void SetSpread(ArtbookEntry? left, Sprite leftSprite, ArtbookEntry? right, Sprite rightSprite)
{
ApplyPage(leftPageImage, leftPageNameText, leftSaveBtn, leftEditBtn, left, leftSprite);
ApplyPage(rightPageImage, rightPageNameText, rightSaveBtn, rightEditBtn, right, rightSprite);
}
public void SetNavigation(bool canPrev, bool canNext)
{
leftArrowBtn.interactable = canPrev;
rightArrowBtn.interactable = canNext;
}
private static void ApplyPage(Image image, TMP_Text nameText, Button saveBtn, Button editBtn,
ArtbookEntry? entry, Sprite sprite)
{
var hasEntry = entry.HasValue;
saveBtn.interactable = hasEntry;
editBtn.interactable = hasEntry;
if (!hasEntry)
{
image.enabled = false;
image.sprite = null;
nameText.text = string.Empty;
return;
}
image.sprite = sprite;
image.enabled = sprite != null;
nameText.text = entry.Value.Name;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d635629f252db53479e66347a37c4f0b

View File

@@ -4,8 +4,11 @@
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:c176ee863a5e74e88a6517f9f102cf92",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -10,7 +10,7 @@ namespace Darkmatter.Features.Colorbook.Installers
{
public void Register(IContainerBuilder builder)
{
builder.RegisterEntryPoint<ColorbookFlowController>(Lifetime.Singleton);
builder.RegisterEntryPoint<ColorbookFlowController>();
}
}
}

View File

@@ -1,16 +1,82 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Loading;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Core.Enums.Services.Scenes;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.Colorbook.System;
public class ColorbookFlowController : IAsyncStartable
public class ColorbookFlowController : IAsyncStartable, IDisposable
{
private readonly IDrawingCatalogController _drawingCatalog;
private readonly ILoadingScreen _loadingScreen;
private readonly IProgressionSystem _progression;
private readonly ISceneService _scenes;
private readonly IEventBus _bus;
private IDisposable _selectedSub;
private IDisposable _returnToMainMenuSubscription;
public ColorbookFlowController(
IDrawingCatalogController drawingCatalog,
ILoadingScreen loadingScreen,
IProgressionSystem progression,
ISceneService scenes,
IEventBus bus)
{
_drawingCatalog = drawingCatalog;
_loadingScreen = loadingScreen;
_progression = progression;
_scenes = scenes;
_bus = bus;
}
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
_returnToMainMenuSubscription = _bus.Subscribe<ReturnToMainMenuSignal>(OnReturnToMainMenu);
_loadingScreen.SetProgress(1f);
await _drawingCatalog.InitializeAsync(cancellation);
_selectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
_loadingScreen.Hide();
}
private void OnReturnToMainMenu(ReturnToMainMenuSignal signal)
{
LoadMainMenuSceneAsync().Forget(UnityEngine.Debug.LogException);
}
private async UniTask LoadMainMenuSceneAsync(CancellationToken ct = default)
{
_loadingScreen.Show();
var progress = new Progress<float>(p => _loadingScreen.SetProgress(p * 0.5f));
await _scenes.LoadSceneAsync(nameof(GameScene.MainMenu), progress, ct);
var mappedProgress = new Progress<float>(p => _loadingScreen.SetProgress(0.5f + p * 0.25f));
await _scenes.UnloadSceneAsync(nameof(GameScene.Colorbook), mappedProgress, ct);
}
private void OnDrawingSelected(DrawingSelectedSignal signal)
{
HandleSelectionAsync(signal.TemplateId).Forget();
}
private async UniTaskVoid HandleSelectionAsync(string templateId)
{
await _progression.SetLastOpenedAsync(templateId);
await _scenes.LoadSceneAsync(GameScene.Gameplay, progress: null, cancellationToken: default);
}
public void Dispose()
{
_selectedSub?.Dispose();
_returnToMainMenuSubscription?.Dispose();
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 02004f7c20c0640778c41a7643c2da82
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Commands;
public class ColorRegionCommand : ICommand
{
private readonly ColorRegionView _view;
private readonly Color _from;
private readonly Color _to;
public ColorRegionCommand(ColorRegionView view, Color from, Color to)
{
_view = view;
_from = from;
_to = to;
}
public void Execute() => _view.SetColor(_to);
public void Undo() => _view.SetColor(_from);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c212dc9aaa7e4445968fee229a715b3b
timeCreated: 1779972906

View File

@@ -1,14 +1,28 @@
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Features.Coloring.Systems;
using Darkmatter.Features.Coloring.UI;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Coloring
{
public class ColoringFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private ColorPaletteHolderView paletteHolderView;
public void Register(IContainerBuilder builder)
{
if (paletteHolderView != null)
{
builder.RegisterComponent(paletteHolderView);
builder.RegisterEntryPoint<ColorPaletteHolderPresenter>().WithParameter(paletteHolderView);
}
builder.Register<IColorButtonFactory, ColorButtonFactory>(Lifetime.Singleton);
builder.Register<ColoringStateRepository>(Lifetime.Singleton);
builder.Register<IColoringController, ColoringController>(Lifetime.Singleton);
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 569e45483f54348a593b6571740cd8a2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public class ColorButtonFactory : IColorButtonFactory
{
private readonly ColorPaletteHolderView _holder;
private readonly ColoringStateRepository _repository;
private readonly ISfxPlayer _sfx;
public ColorButtonFactory(
ColorPaletteHolderView holder,
ColoringStateRepository repository,
ISfxPlayer sfx)
{
_holder = holder;
_repository = repository;
_sfx = sfx;
}
public ColorButton Create(GameObject prefab, Color color)
{
var go = Object.Instantiate(prefab, _holder.SpawnRoot);
var btn = go.GetComponent<ColorButton>();
btn.Setup(color, _repository, _sfx);
return btn;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bba9a928a76471391068acd46c4f684
timeCreated: 1779970637

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Static.Features.Coloring;
using Darkmatter.Features.Coloring.Commands;
using Darkmatter.Features.Coloring.UI;
using Darkmatter.Libs.Observer;
using UnityEngine;
using ZLinq;
namespace Darkmatter.Features.Coloring.Systems;
public class ColoringController : IColoringController, IDisposable
{
private const string ColorButtonPrefabKey = "colorButton";
private readonly ColoringStateRepository _repository;
private readonly IColorButtonFactory _buttonFactory;
private readonly IEventBus _bus;
private readonly IAssetProviderService _assetProviderService;
private readonly IUndoStack _history;
private readonly IGameplaySceneRefs _refs;
private GameObject _colorInstance;
private GameObject _colorButtonPrefab;
private readonly List<ColorRegionView> _regions = new();
private readonly List<ColorButton> _buttons = new();
public ColoringController(
ColoringStateRepository repository,
IColorButtonFactory buttonFactory,
IEventBus bus,
IAssetProviderService assetProviderService,
IUndoStack history,
IGameplaySceneRefs refs)
{
_repository = repository;
_buttonFactory = buttonFactory;
_bus = bus;
_assetProviderService = assetProviderService;
_history = history;
_refs = refs;
}
public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct)
{
Clear();
await TryLoadColorButtonPrefabAsync(ct);
ct.ThrowIfCancellationRequested();
await TryLoadColorPaletteAsync(template, ct);
ct.ThrowIfCancellationRequested();
CreateColorButtonInstances();
InitializeColorRegions(template, savedColors);
}
public void PaintRegion(string regionId, Color color)
{
var region = _regions.AsValueEnumerable().FirstOrDefault(r => r.RegionId == regionId);
if (region == null) return;
var from = region.Color;
_history.Push(new ColorRegionCommand(region, from, color));
_bus.Publish(new ColorAppliedSignal(regionId, color));
}
public IReadOnlyDictionary<string, Color> GetCurrentColors()
{
var snapshot = new Dictionary<string, Color>(_regions.Count);
foreach (var region in _regions)
snapshot[region.RegionId] = region.Color;
return snapshot;
}
public void Clear()
{
_history.Drop();
foreach (var button in _buttons)
if (button != null) UnityEngine.Object.Destroy(button.gameObject);
_buttons.Clear();
_regions.Clear();
if (_colorInstance != null)
{
UnityEngine.Object.Destroy(_colorInstance);
_colorInstance = null;
}
}
public void Dispose() => Clear();
private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors)
{
_colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot);
var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true);
foreach (var region in views)
{
var id = region.RegionId;
var authoredColor = region.Color;
var resumeColor = savedColors != null && savedColors.TryGetValue(id, out var saved)
? saved
: authoredColor;
if (resumeColor != authoredColor)
_history.Append(new ColorRegionCommand(region, authoredColor, resumeColor));
_regions.Add(region);
region.Initialize(resumeColor, () => PaintRegion(id, _repository.SelectedColor));
}
}
private async UniTask TryLoadColorButtonPrefabAsync(CancellationToken ct)
{
if (_colorButtonPrefab != null) return;
_colorButtonPrefab = await _assetProviderService.LoadAssetAsync<GameObject>(
ColorButtonPrefabKey, progress: null, cancellationToken: ct);
if (_colorButtonPrefab == null)
throw new Exception($"No color button prefab at '{ColorButtonPrefabKey}'");
}
private async UniTask TryLoadColorPaletteAsync(IDrawingTemplate template, CancellationToken ct)
{
var palette = await _assetProviderService.LoadAssetAsync<ColorPaletteSO>(
template.ColorPaletteId, progress: null, cancellationToken: ct);
if (palette == null)
throw new Exception($"No color palette at '{template.ColorPaletteId}'");
_repository.SetPalette(palette);
}
private void CreateColorButtonInstances()
{
foreach (var color in _repository.SelectedPalette.Colors)
_buttons.Add(_buttonFactory.Create(_colorButtonPrefab, color));
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0849f8cc757842278c38ee6f4d61b985
timeCreated: 1779971175

View File

@@ -0,0 +1,33 @@
using System;
using Darkmatter.Core.Contracts.Features.Coloring;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public class ColoringStateRepository
{
public IColorPalette SelectedPalette { get; private set; }
public int SelectedIndex { get; private set; }
public Color SelectedColor =>
SelectedPalette != null && SelectedIndex >= 0 && SelectedIndex < SelectedPalette.Colors.Count
? SelectedPalette.Colors[SelectedIndex]
: Color.white;
public event Action SelectedIndexChanged;
public void SetPalette(IColorPalette palette)
{
SelectedPalette = palette;
SelectedIndex = 0;
SelectedIndexChanged?.Invoke();
}
public void SelectColor(Color color)
{
if (SelectedPalette == null) return;
var idx = SelectedPalette.Colors.IndexOf(color);
if (idx < 0) return;
SelectedIndex = idx;
SelectedIndexChanged?.Invoke();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a288824e4f554000994bec451c09957c
timeCreated: 1779970861

View File

@@ -0,0 +1,9 @@
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public interface IColorButtonFactory
{
ColorButton Create(GameObject prefab,Color color);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 91b71be7f45a41cbb17492f82a3f0967
timeCreated: 1779970564

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 416f96737010f40c085ac404743fa8ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Enums.Services.Audio;
using Darkmatter.Features.Coloring.Systems;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(Button))]
public class ColorButton : MonoBehaviour
{
[SerializeField] private Image swatch;
[SerializeField] private GameObject selectedUI;
private Button _button;
private Color _color;
private ColoringStateRepository _repository;
private ISfxPlayer _sfx;
private void Awake()
{
_button = GetComponent<Button>();
}
public void Setup(Color color, ColoringStateRepository repository, ISfxPlayer sfx)
{
_color = color;
_repository = repository;
_sfx = sfx;
swatch.color = color;
_button.onClick.AddListener(OnClick);
_repository.SelectedIndexChanged += UpdateSelectedUI;
UpdateSelectedUI();
}
private void OnClick()
{
_sfx.Play(SfxId.UiTap);
_repository.SelectColor(_color);
}
private void UpdateSelectedUI()
{
if (selectedUI != null)
selectedUI.SetActive(_repository.SelectedColor == _color);
}
private void OnDestroy()
{
if (_button != null) _button.onClick.RemoveListener(OnClick);
if (_repository != null) _repository.SelectedIndexChanged -= UpdateSelectedUI;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7e64b829b90144faa3bd2c7f5c0ad291
timeCreated: 1779970210

View File

@@ -0,0 +1,32 @@
using System;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.Coloring.UI
{
public class ColorPaletteHolderPresenter : IStartable, IDisposable
{
private readonly ColorPaletteHolderView _view;
private readonly IEventBus _bus;
private IDisposable _assembledSub;
public ColorPaletteHolderPresenter(ColorPaletteHolderView view, IEventBus bus)
{
_view = view;
_bus = bus;
}
public void Start()
{
_view.HideInstant();
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Show());
}
public void Dispose()
{
_assembledSub?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cc0590d8f6fce4487943575bfb96c32a

View File

@@ -0,0 +1,76 @@
using PrimeTween;
using UnityEngine;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(CanvasGroup))]
public class ColorPaletteHolderView : MonoBehaviour
{
[SerializeField] private RectTransform spawnRoot;
[SerializeField] private RectTransform animatedRoot;
[SerializeField] private float showDuration = 0.35f;
[SerializeField] private float hideDuration = 0.25f;
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
private CanvasGroup _canvasGroup;
private Vector2 _shownAnchoredPos;
private Sequence _activeSequence;
public RectTransform SpawnRoot => spawnRoot;
private void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
if (animatedRoot == null) animatedRoot = (RectTransform)transform;
_shownAnchoredPos = animatedRoot.anchoredPosition;
}
public Sequence Show()
{
KillActive();
gameObject.SetActive(true);
_canvasGroup.interactable = true;
_canvasGroup.blocksRaycasts = true;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, _shownAnchoredPos, showDuration, Ease.OutBack))
.Group(Tween.Alpha(_canvasGroup, 1f, showDuration, Ease.OutQuad));
return _activeSequence;
}
public Sequence Hide()
{
KillActive();
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
var hiddenPos = _shownAnchoredPos + hiddenOffset;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, hiddenPos, hideDuration, Ease.InQuad))
.Group(Tween.Alpha(_canvasGroup, 0f, hideDuration, Ease.InQuad))
.ChainCallback(() => gameObject.SetActive(false));
return _activeSequence;
}
public void HideInstant()
{
KillActive();
animatedRoot.anchoredPosition = _shownAnchoredPos + hiddenOffset;
_canvasGroup.alpha = 0f;
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
gameObject.SetActive(false);
}
private void KillActive()
{
if (_activeSequence.isAlive) _activeSequence.Stop();
_activeSequence = default;
}
private void OnDisable()
{
KillActive();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c960fbf3dc5a54f5399178665d20b1ce

View File

@@ -0,0 +1,44 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(Image))]
public class ColorRegionView : MonoBehaviour, IPointerClickHandler
{
[field: SerializeField] public string RegionId { get; private set; }
[SerializeField, Range(0f, 1f)] private float alphaHitThreshold = 0.5f;
public Color Color => _image.color;
private Image _image;
private event Action OnRegionClicked;
private void Awake()
{
_image = GetComponent<Image>();
_image.alphaHitTestMinimumThreshold = alphaHitThreshold;
}
public void Initialize(Color color, Action onRegionClicked)
{
_image.color = color;
OnRegionClicked = onRegionClicked;
}
public void SetColor(Color color)
{
_image.color = color;
}
public void OnPointerClick(PointerEventData eventData)
{
OnRegionClicked?.Invoke();
}
private void OnDestroy()
{
OnRegionClicked = null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7667901d8aea645d28b5125a2ed546ce

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Data.Signals.Features.Drawing;

View File

@@ -1,8 +1,8 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.DrawingCatalog;
namespace Darkmatter.Features.DrawingCatalog
{
public class DrawingCatalogButton : MonoBehaviour
{
public string Id { get; private set; }
@@ -16,3 +16,5 @@ public class DrawingCatalogButton : MonoBehaviour
button.onClick.AddListener(onClick);
}
}
}

View File

@@ -2,31 +2,82 @@ using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Features.DrawingCatalog.Systems;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
using UnityEngine;
namespace Darkmatter.Features.DrawingCatalog
{
public class DrawingCatalogPresenter : IStartable, IDisposable
{
private int _currentPage = 0;
private int _totalPages = 1;
private const int ItemPerPage = 8; //2 rows x 4 columns
private readonly DrawingCatalogView _view;
private readonly IDrawingCatalogController _controller;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IEventBus _eventBus;
private readonly CancellationTokenSource _cts = new();
private IDisposable _openSubscription;
public DrawingCatalogPresenter(DrawingCatalogView view, DrawingCatalogController controller,
IDrawingTemplateCatalog catalog)
public DrawingCatalogPresenter(DrawingCatalogView view, IDrawingCatalogController controller,
IDrawingTemplateCatalog catalog, IEventBus eventBus)
{
_view = view;
_controller = controller;
_catalog = catalog;
_eventBus = eventBus;
}
public void Start()
{
_view.OnItemClicked += OnItemClicked;
_view.OnBackClicked += OnBackBtnClicked;
_view.OnArtBookClicked += OnArtBookBtnClicked;
_view.OnLeftPageClicked += OnLeftPageBtnClicked;
_view.OnRightPageClicked += OnRightPageBtnClicked;
_controller.ListChanged += OnListChanged;
_openSubscription = _eventBus.Subscribe<OpenColorBookSignal>(HandleOpenColorBookSignal);
}
private void HandleOpenColorBookSignal(OpenColorBookSignal signal)
{
_view.Show();
}
private void OnRightPageBtnClicked()
{
if (_currentPage < _totalPages - 1)
{
_currentPage++;
_view.SetPagination(_currentPage, _totalPages);
}
}
private void OnLeftPageBtnClicked()
{
if (_currentPage > 0)
{
_currentPage--;
_view.SetPagination(_currentPage, _totalPages);
}
}
private void OnArtBookBtnClicked()
{
_eventBus.Publish(new OpenArtBookSignal());
_view.Hide();
}
private void OnBackBtnClicked()
{
_eventBus.Publish(new ReturnToMainMenuSignal());
Debug.Log("Back button clicked in Drawing Catalog");
}
private void OnItemClicked(string id) =>
@@ -39,6 +90,10 @@ namespace Darkmatter.Features.DrawingCatalog
{
var ids = _controller.VisibleIds;
var vms = new List<CatalogItemVM>(ids.Count);
_totalPages = (int)Math.Ceiling(ids.Count / (float)ItemPerPage);
_currentPage = 0;
foreach (var id in ids)
{
if (ct.IsCancellationRequested) return;
@@ -47,13 +102,21 @@ namespace Darkmatter.Features.DrawingCatalog
}
if (ct.IsCancellationRequested) return;
_view.ResetScrollPosition();
_view.SetItems(vms);
_view.SetPagination(_currentPage, _totalPages);
}
public void Dispose()
{
_view.OnItemClicked -= OnItemClicked;
_controller.ListChanged -= OnListChanged;
_view.OnBackClicked -= OnBackBtnClicked;
_view.OnArtBookClicked -= OnArtBookBtnClicked;
_view.OnLeftPageClicked -= OnLeftPageBtnClicked;
_view.OnRightPageClicked -= OnRightPageBtnClicked;
_openSubscription?.Dispose();
_cts.Cancel();
_cts.Dispose();
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using Darkmatter.Libs.Observer;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
namespace Darkmatter.Features.DrawingCatalog
{
@@ -9,9 +11,40 @@ namespace Darkmatter.Features.DrawingCatalog
{
[SerializeField] private RectTransform content;
[SerializeField] private DrawingCatalogButton buttonPrefab;
[Header("Header Buttons")]
[SerializeField] private Button backButton;
[SerializeField] private Button artBookButton;
[Header("Scroll Pagination")]
[SerializeField] private ScrollRect scrollRect;
[SerializeField] private Button leftPageButton;
[SerializeField] private Button rightPageButton;
private readonly List<DrawingCatalogButton> _buttons = new();
public event Action<string> OnItemClicked;
public event Action OnBackClicked;
public event Action OnArtBookClicked;
public event Action OnLeftPageClicked;
public event Action OnRightPageClicked;
private Coroutine _sliderCoroutine;
public void Start()
{
backButton.onClick.AddListener(() => OnBackClicked?.Invoke());
artBookButton.onClick.AddListener(() => OnArtBookClicked?.Invoke());
leftPageButton.onClick.AddListener(() => OnLeftPageClicked?.Invoke());
rightPageButton.onClick.AddListener(() => OnRightPageClicked?.Invoke());
}
public void Show()
{
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
public void SetItems(IReadOnlyList<CatalogItemVM> items)
{
@@ -30,6 +63,45 @@ namespace Darkmatter.Features.DrawingCatalog
_buttons.RemoveAt(_buttons.Count - 1);
Destroy(last.gameObject);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
}
public void SetPagination(int currentPage, int totalPages)
{
leftPageButton.interactable = currentPage > 0;
rightPageButton.interactable = currentPage < totalPages - 1;
if (totalPages <= 1) return;
float pageWidth = scrollRect.viewport.rect.width;
float maxScroll = content.rect.width - pageWidth;
if (maxScroll <= 0f) return;
float targetPosition = Mathf.Clamp01(currentPage * pageWidth / maxScroll);
if (_sliderCoroutine != null)
StopCoroutine(_sliderCoroutine);
_sliderCoroutine = StartCoroutine(SmoothScrollTo(targetPosition));
}
private System.Collections.IEnumerator SmoothScrollTo(float targetPosition)
{
const float duration = 0.3f;
float elapsed = 0f;
float startPosition = scrollRect.horizontalNormalizedPosition;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
scrollRect.horizontalNormalizedPosition = Mathf.Lerp(startPosition, targetPosition, elapsed / duration);
yield return null;
}
scrollRect.horizontalNormalizedPosition = targetPosition;
}
public void ResetScrollPosition()
{
scrollRect.horizontalNormalizedPosition = 0f;
}
}
}

View File

@@ -3,8 +3,12 @@
"rootNamespace": "Darkmatter.Features.GameplayFlow",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:c176ee863a5e74e88a6517f9f102cf92",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1"
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,5 +1,6 @@
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Features.GameplayFlow.SceneRefs;
using Darkmatter.Features.GameplayFlow.Systems;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
@@ -15,6 +16,10 @@ namespace Darkmatter.Features.GameplayFlow
{
if (sceneRefs != null)
builder.RegisterComponent<IGameplaySceneRefs>(sceneRefs);
builder.Register<GameplayFlowController>(Lifetime.Singleton)
.As<IGameplayFlowController>()
.As<IAsyncStartable>();
}
}
}

View File

@@ -6,19 +6,7 @@ namespace Darkmatter.Features.GameplayFlow.SceneRefs
public class GameplaySceneRefs : MonoBehaviour, IGameplaySceneRefs
{
[SerializeField] private RectTransform paperRoot;
[SerializeField] private RectTransform slotsParent;
[SerializeField] private RectTransform piecesParent;
[SerializeField] private RectTransform regionsParent;
[SerializeField] private RectTransform hudRoot;
[SerializeField] private RectTransform trayPanel;
public RectTransform PaperRoot => paperRoot;
public RectTransform SlotsParent => slotsParent;
public RectTransform PiecesParent => piecesParent;
public RectTransform RegionsParent => regionsParent;
public RectTransform HudRoot => hudRoot;
public RectTransform TrayPanel => trayPanel;
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef06e7bcb7ee346d894886c4a4848bcd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Enums.Features.Progression;
using Darkmatter.Core.Enums.Services.Scenes;
using Darkmatter.Libs.Observer;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Features.GameplayFlow.Systems
{
public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable
{
private const int AutosaveDebounceMs = 500;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IShapeBuilderController _shapeBuilder;
private readonly IColoringController _coloring;
private readonly ISceneService _scenes;
private readonly IEventBus _bus;
private IDrawingTemplate _template;
private string _templateId;
private DrawingPhase _phase = DrawingPhase.ShapeBuilding;
private IDisposable _assembledSub;
private IDisposable _colorAppliedSub;
private CancellationTokenSource _autosaveCts;
private CancellationTokenSource _scopeCts;
public GameplayFlowController(
IProgressionSystem progression,
IDrawingTemplateCatalog catalog,
IShapeBuilderController shapeBuilder,
IColoringController coloring,
ISceneService scenes,
IEventBus bus)
{
_progression = progression;
_catalog = catalog;
_shapeBuilder = shapeBuilder;
_coloring = coloring;
_scenes = scenes;
_bus = bus;
}
public async UniTask StartAsync(CancellationToken cancellation)
{
_scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
var ct = _scopeCts.Token;
_templateId = _progression.LastOpenedTemplateId;
if (string.IsNullOrEmpty(_templateId))
throw new Exception("[GameplayFlow] No LastOpenedTemplateId set. ColorbookFlow must call _progression.SetLastOpenedAsync(id) before loading Gameplay.");
_template = await _catalog.LoadAsync(_templateId);
ct.ThrowIfCancellationRequested();
var progress = _progression.GetProgress(_templateId);
_phase = progress?.phase ?? DrawingPhase.ShapeBuilding;
await _shapeBuilder.InitializeAsync(ct);
ct.ThrowIfCancellationRequested();
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
if (_phase == DrawingPhase.Coloring)
{
// Resume direct into coloring: pre-snap every piece. ShapeBuilder
// emits ShapeAssembledSignal once counter hits expected; that path
// also triggers InitializeColoring with savedColors.
var allIds = CollectAllPieceIds(_template);
await _shapeBuilder.BuildAsync(_template, allIds, ct);
}
else
{
await _shapeBuilder.BuildAsync(_template, progress?.snappedPieces, ct);
}
}
public async UniTask BackAsync()
{
await SaveCurrentAsync(captureThumbnail: _phase == DrawingPhase.Coloring);
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: default);
}
public async UniTask SaveAsync()
{
// Explicit user-pressed save — capture thumbnail + (planned) native gallery export.
await SaveCurrentAsync(captureThumbnail: true);
// TODO: route through ICaptureService + IGalleryService once those exist.
}
public async UniTask NextAsync()
{
await SaveCurrentAsync(captureThumbnail: true);
_progression.MarkCompleted(_templateId);
var nextId = _catalog.GetNextTemplate(_templateId);
if (string.IsNullOrEmpty(nextId))
{
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: default);
return;
}
await _progression.SetLastOpenedAsync(nextId);
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(GameScene.Gameplay, progress: null, cancellationToken: default);
}
public void OnApplicationPaused()
{
// Fire-and-forget — pause window is small; PlayerPrefs write is sync under the hood.
SaveCurrentAsync(captureThumbnail: false).Forget();
}
private void OnShapeAssembled(ShapeAssembledSignal signal)
{
if (signal.TemplateId != _templateId) return;
EnterColoringAsync().Forget();
}
private async UniTask EnterColoringAsync()
{
_phase = DrawingPhase.Coloring;
var progress = _progression.GetProgress(_templateId);
var savedColors = ToColorDict(progress?.regionColors);
await _coloring.InitializeRegionsAsync(_template, savedColors, _scopeCts.Token);
// Bare-assembled snapshot — catalog should show the puzzle outline even if
// the child never paints (per readme §12b save matrix).
await SaveCurrentAsync(captureThumbnail: true);
}
private void OnColorApplied(ColorAppliedSignal _)
{
if (_phase != DrawingPhase.Coloring) return;
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = CancellationTokenSource.CreateLinkedTokenSource(_scopeCts.Token);
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
}
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
{
try
{
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
await SaveCurrentAsync(captureThumbnail: false);
}
catch (OperationCanceledException) { /* superseded by next paint or scope end */ }
}
private async UniTask SaveCurrentAsync(bool captureThumbnail)
{
if (string.IsNullOrEmpty(_templateId)) return;
var snappedIds = new List<string>(_shapeBuilder.GetSnappedPieceIds());
var regionEntries = new List<RegionColorEntry>();
foreach (var kv in _coloring.GetCurrentColors())
regionEntries.Add(new RegionColorEntry { regionId = kv.Key, color = kv.Value });
var existing = _progression.GetProgress(_templateId);
var progress = new DrawingProgress
{
templateId = _templateId,
phase = _phase,
snappedPieces = snappedIds,
regionColors = regionEntries,
hasThumbnail = captureThumbnail || (existing?.hasThumbnail ?? false),
hasBeenCompleted = existing?.hasBeenCompleted ?? false,
completionCount = existing?.completionCount ?? 0,
UpdatedUtc = DateTime.UtcNow,
FirstCompletedUtc = existing?.FirstCompletedUtc,
};
byte[] thumbnailPng = null;
if (captureThumbnail)
{
// TODO: replace with ICaptureService.CaptureAsync() once available.
// For now we record hasThumbnail=true but write no PNG.
}
await _progression.SaveProgressAsync(progress, thumbnailPng);
}
private static IReadOnlyCollection<string> CollectAllPieceIds(IDrawingTemplate template)
{
var ids = new List<string>(template.Pieces.Count);
foreach (var p in template.Pieces) ids.Add(p.Id);
return ids;
}
private static IReadOnlyDictionary<string, Color> ToColorDict(List<RegionColorEntry> entries)
{
var dict = new Dictionary<string, Color>();
if (entries == null) return dict;
foreach (var e in entries)
if (!string.IsNullOrEmpty(e.regionId))
dict[e.regionId] = e.color;
return dict;
}
public void Dispose()
{
_assembledSub?.Dispose();
_colorAppliedSub?.Dispose();
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_scopeCts?.Cancel();
_scopeCts?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 54c118b4507054746befcaa85a1e9578

View File

@@ -24,6 +24,14 @@ namespace Darkmatter.Features.History
OnStackChanged?.Invoke();
}
public void Append(ICommand cmd)
{
_undo.AddLast(cmd);
if (_undo.Count > Capacity) _undo.RemoveFirst();
_redo.Clear();
OnStackChanged?.Invoke();
}
public void Undo()
{
if (!CanUndo) return;
@@ -50,5 +58,12 @@ namespace Darkmatter.Features.History
_redo.Clear();
OnStackChanged?.Invoke();
}
public void Drop()
{
_undo.Clear();
_redo.Clear();
OnStackChanged?.Invoke();
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d5d2ca79d314453cba062c16dce8181
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Loading
{
public class LoadingFeatureModule : MonoBehaviour,IModule
{
[SerializeField] private LoadingScreenView loadingScreenView;
public void Register(IContainerBuilder builder)
{
builder.RegisterEntryPoint<LoadingPresenter>().WithParameter(loadingScreenView);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 673a21bf7c6bb430eadece2a989d79ac

View File

@@ -1,85 +0,0 @@
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Loading
{
public class LoadingScreenView : MonoBehaviour
{
[SerializeField] private Slider loadingSlider;
[SerializeField] private TMP_Text statusText;
[SerializeField] private float loadingSmoothTime = 10f;
[SerializeField] private float messageHoldDuration = 2f;
[SerializeField] private float messageFadeDuration = 0.4f;
public float LoadingSmoothTime => loadingSmoothTime;
private static readonly string[] StatusLines = {
"Blue is arguing with red...",
"The crayons are dancing...",
"Yellow spilled everywhere...",
"Chasing escaped paint drops...",
"Untying rainbow knots...",
"Cleaning glitter explosions...",
"The color monsters are hungry...",
"Mixing mystery colors...",
"Someone ate the purple paint...",
"Teaching green to behave..."
};
private Sequence _statusSequence;
private int _statusIndex;
public void Show()
{
gameObject.SetActive(true);
StartStatusCycle();
}
public void Hide()
{
StopStatusCycle();
gameObject.SetActive(false);
}
public void SetProgress(float progress)
{
if (loadingSlider != null) loadingSlider.value = progress;
}
private void StartStatusCycle()
{
if (statusText == null || StatusLines.Length == 0) return;
StopStatusCycle();
_statusIndex = Random.Range(0, StatusLines.Length);
statusText.text = StatusLines[_statusIndex];
statusText.alpha = 0f;
_statusSequence = Sequence.Create(useUnscaledTime: true, cycles: -1)
.Chain(Tween.Alpha(statusText, 1f, messageFadeDuration, Ease.OutQuad))
.ChainDelay(messageHoldDuration)
.Chain(Tween.Alpha(statusText, 0f, messageFadeDuration, Ease.InQuad))
.ChainCallback(AdvanceStatusLine);
}
private void StopStatusCycle()
{
if (_statusSequence.isAlive) _statusSequence.Stop();
_statusSequence = default;
}
private void AdvanceStatusLine()
{
if (statusText == null) return;
_statusIndex = (_statusIndex + 1) % StatusLines.Length;
statusText.text = StatusLines[_statusIndex];
}
private void OnDisable()
{
StopStatusCycle();
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43e5eadd1991e4cbcb672bf0fd4dfde9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Loading
{
public class LoadingScreenView : MonoBehaviour
{
[SerializeField] private Slider loadingSlider;
[SerializeField] private TMP_Text statusText;
[SerializeField] private float loadingSmoothTime = 10f;
[SerializeField] private float dotInterval = 0.4f;
public float LoadingSmoothTime => loadingSmoothTime;
private static readonly string[] StatusLines = {
"Blue is arguing with red",
"The crayons are dancing",
"Yellow spilled everywhere",
"Chasing escaped paint drops",
"Untying rainbow knots",
"Cleaning glitter explosions",
"The color monsters are hungry",
"Mixing mystery colors",
"Someone ate the purple paint",
"Teaching green to behave"
};
private static readonly string[] DotFrames = { ".", "..", "..." };
private string _baseLine;
private int _dotIndex;
private Sequence _dotSequence;
public void Show()
{
gameObject.SetActive(true);
if (statusText == null || StatusLines.Length == 0) return;
_baseLine = StatusLines[Random.Range(0, StatusLines.Length)];
_dotIndex = 0;
statusText.text = _baseLine + DotFrames[_dotIndex];
StartDotAnimation();
}
public void Hide()
{
StopDotAnimation();
gameObject.SetActive(false);
}
public void SetProgress(float progress)
{
if (loadingSlider != null) loadingSlider.value = progress;
}
private void StartDotAnimation()
{
StopDotAnimation();
_dotSequence = Sequence.Create(useUnscaledTime: true, cycles: -1)
.ChainDelay(dotInterval)
.ChainCallback(AdvanceDots);
}
private void StopDotAnimation()
{
if (_dotSequence.isAlive) _dotSequence.Stop();
_dotSequence = default;
}
private void AdvanceDots()
{
if (statusText == null || string.IsNullOrEmpty(_baseLine)) return;
_dotIndex = (_dotIndex + 1) % DotFrames.Length;
statusText.text = _baseLine + DotFrames[_dotIndex];
}
private void OnDisable()
{
StopDotAnimation();
}
}
}

View File

@@ -7,7 +7,8 @@
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:68765d262e2128e4ab49c983f3411946",
"GUID:80ecb87cae9c44d19824e70ea7229748",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,4 +1,7 @@
using System;
using Darkmatter.Core.Data.Signals.Features.AppBoot;
using Darkmatter.Core.Data.Signals.Features.MainMenu;
using Darkmatter.Libs.Observer;
using UnityEngine;
using VContainer.Unity;
@@ -6,21 +9,31 @@ namespace Darkmatter.Features.Mainmenu
{
public class MainMenuPresenter : IStartable, IDisposable
{
private readonly IEventBus _eventBus;
private readonly MainMenuView _view;
public MainMenuPresenter(MainMenuView view)
public MainMenuPresenter(MainMenuView view, IEventBus eventBus)
{
_view = view;
_eventBus = eventBus;
}
public void Start()
{
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
}
private void OnIntroComplete(IntroCompletedSignal signal)
{
_view.PlayMascotIntro();
}
private void OnPlayBtnClicked()
{
Debug.Log("Play Btn Clicked");
_eventBus.Publish(new PlayBtnClickedSignal());
}
public void Dispose()

View File

@@ -10,10 +10,8 @@ namespace Darkmatter.Features.Mainmenu
public class MainMenuView : MonoBehaviour
{
[Header("UI Elements")]
[SerializeField] private Button playBtn;
[SerializeField] private ParticleSystem playBtnParticle;
[SerializeField] private Animator playBtnAnimator;
//[SerializeField] private Button playBtn;
[SerializeField] private PlayButtonView playBtn;
[SerializeField] private SkeletonGraphic mascotSkeletonGraphic;
[Header("Mascot Animations")]
@@ -28,8 +26,7 @@ namespace Darkmatter.Features.Mainmenu
private void Start()
{
playBtn.onClick.AddListener(OnPlayBtnClicked);
PlayMascotIntro();
playBtn.OnPlayBtnClickedEvent += () => OnPlayBtnClickedEvent?.Invoke();
}
private void OnDisable()
@@ -39,7 +36,7 @@ namespace Darkmatter.Features.Mainmenu
helloCts = null;
}
private void PlayMascotIntro()
public void PlayMascotIntro()
{
var state = mascotSkeletonGraphic.AnimationState;
state.SetAnimation(0, jumpAnimation, false);
@@ -59,19 +56,5 @@ namespace Darkmatter.Features.Mainmenu
state.AddAnimation(0, idleAnimation, true, 0f);
}
}
private void OnPlayBtnClicked()
{
playBtn.interactable = false;
playBtnAnimator.enabled = false;
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
}
private async UniTaskVoid PlayBtnSequenceAsync(CancellationToken token)
{
playBtnParticle.Play();
await UniTask.WaitUntil(() => !playBtnParticle.IsAlive(true), cancellationToken: token);
OnPlayBtnClickedEvent?.Invoke();
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Mainmenu
{
public class PlayButtonView : MonoBehaviour
{
private Button playBtn;
private Animator playBtnAnimator;
[SerializeField] private ParticleSystem playBtnParticle;
public event Action OnPlayBtnClickedEvent;
private void Awake()
{
playBtnAnimator = GetComponent<Animator>();
playBtn = GetComponent<Button>();
}
private void Start()
{
playBtn.onClick.AddListener(OnPlayBtnClicked);
}
private void OnPlayBtnClicked()
{
playBtn.interactable = false;
playBtnAnimator.enabled = false;
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
}
private async UniTaskVoid PlayBtnSequenceAsync(CancellationToken token)
{
playBtnParticle.Play();
await UniTask.WaitUntil(() => !playBtnParticle.IsAlive(true), cancellationToken: token);
OnPlayBtnClickedEvent?.Invoke();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 26f12f86a29f25046a03d5f29e123beb

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4bb322f193284fd9b4ebb75c3916408
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
{
"name": "Features.MainMenuFlow",
"rootNamespace": "Darkmatter.Features.MainMenuFlow",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:729fabb77852b4d3eae2417d9564dc37",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 70ab456139c5746eab129dca59e89914
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cb104c5c062454ec3880d2bd33072c28
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Loading;
using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Signals.Features.MainMenu;
using Darkmatter.Core.Enums.Services.Scenes;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.MainMenuFlow.Flow
{
public class MainMenuFlow : IAsyncStartable, IDisposable
{
private readonly ILoadingScreen _loadingScreen;
private readonly ISceneService _sceneService;
private readonly IEventBus _eventBus;
private IDisposable _playBtnClickedSubscription;
public MainMenuFlow(ILoadingScreen loadingScreen, ISceneService sceneService,IEventBus eventBus)
{
_loadingScreen = loadingScreen;
_sceneService = sceneService;
_eventBus = eventBus;
}
public UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
_playBtnClickedSubscription = _eventBus.Subscribe<PlayBtnClickedSignal>(OnPlayBtnClicked);
_loadingScreen.SetProgress(1f);
_loadingScreen.Hide();
return UniTask.CompletedTask;
}
private void OnPlayBtnClicked(PlayBtnClickedSignal obj)
{
LoadColorbookSceneAsync().Forget();
}
private async UniTask LoadColorbookSceneAsync(CancellationToken ct = default)
{
_loadingScreen.Show();
var progress = new Progress<float>(p => _loadingScreen.SetProgress(p * 0.5f));
await _sceneService.LoadSceneAsync(nameof(GameScene.Colorbook), progress, ct);
var mappedProgress = new Progress<float>(p => _loadingScreen.SetProgress(0.5f + p * 0.25f));
await _sceneService.UnloadSceneAsync(nameof(GameScene.MainMenu),mappedProgress, ct);
}
public void Dispose()
{
_playBtnClickedSubscription?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d8d9b6d57244943f8af779f3a8c58b69

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a84095e2dae4453d83dd936705b54bb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.MainMenuFlow
{
public class MainMenuFlowFeatureModule : MonoBehaviour, IModule
{
public void Register(IContainerBuilder builder)
{
builder.RegisterEntryPoint<Flow.MainMenuFlow>();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 537a5a77a85a141519e96811f4f4e0a3

View File

@@ -14,8 +14,10 @@ public sealed class ProgressionRepository
private const string ThumbnailsFolder = "thumbnails";
private readonly Dictionary<string, DrawingProgress> _records = new();
private string _lastOpenedTemplateId;
public IEnumerable<DrawingProgress> AllProgress() => _records.Values;
public string LastOpenedTemplateId => _lastOpenedTemplateId;
public DrawingProgress? TryGet(string templateId) =>
_records.TryGetValue(templateId, out var p) ? p : (DrawingProgress?)null;
@@ -45,6 +47,14 @@ public sealed class ProgressionRepository
_records[rec.templateId] = rec;
}
_lastOpenedTemplateId = root.lastOpenedTemplateId;
return UniTask.CompletedTask;
}
public UniTask SetLastOpenedAsync(string templateId)
{
_lastOpenedTemplateId = templateId;
FlushRoot();
return UniTask.CompletedTask;
}
@@ -121,6 +131,7 @@ public sealed class ProgressionRepository
var root = new ProgressionRootDto
{
records = _records.Values.ToList(),
lastOpenedTemplateId = _lastOpenedTemplateId,
};
var json = JsonUtility.ToJson(root);
ProtectedPlayerPrefs.SetString(PlayerPrefsKeys.Progression, json);

Some files were not shown because too many files have changed in this diff Show More