Progression. and Drawing Catalog

This commit is contained in:
Savya Bikram Shah
2026-05-27 18:56:17 +05:45
parent 340c24cbfb
commit 93950e8bc6
236 changed files with 41978 additions and 60 deletions

View File

@@ -13,7 +13,7 @@ namespace Darkmatter.App.LifetimeScopes
{
foreach (var module in serviceModules)
{
if (module is IServiceModule serviceModule)
if (module is IModule serviceModule)
serviceModule.Register(builder);
}
}

View File

@@ -6,6 +6,6 @@ namespace Darkmatter.Core.Contracts.Features.Coloring
public interface IColorPalette
{
string Id { get; }
IReadOnlyList<Color> Colors { get; }
List<Color> Colors { get; }
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
namespace Darkmatter.Core.Contracts.Features.DrawingCatalog;
public interface IDrawingCatalogController
{
IReadOnlyList<string> VisibleIds { get; }
event Action ListChanged;
UniTask InitializeAsync(CancellationToken ct);
void OnTemplateSelected(string id);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b0732a5de2a84833ab4d3252d9e22851
timeCreated: 1779881997

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic;
using Darkmatter.Core.Data.Dynamic.Features.Coloring;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using UnityEngine;
namespace Darkmatter.Core.Contracts.Features.Drawing
namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
{
public interface IDrawingTemplate
{

View File

@@ -2,15 +2,15 @@ using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Darkmatter.Core.Contracts.Features.Drawing
namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
{
public interface IDrawingTemplateCatalog
{
UniTask InitializeAsync();
UniTask FetchAsync();
IReadOnlyList<string> AllTemplateIds { get; }
UniTask<Sprite> GetThumbnailAsync(string id);
UniTask<IDrawingTemplate> LoadAsync(string id);
void Release(string id);
string NextUnseen(string currentId);
void ReleaseAll();
string GetNextTemplate(string currentId);
}
}

View File

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

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using UnityEngine;
// Code/Core/Contracts/Features/Progression/IProgressionSystem.cs
namespace Darkmatter.Core.Contracts.Features.Progression
{
public interface IProgressionSystem
{
UniTask LoadAsync();
UniTask SaveAsync();
IReadOnlyCollection<string> CompletedTemplateIds { get; }
void MarkCompleted(string templateId);
DrawingProgress? GetProgress(string templateId);
UniTask SaveProgressAsync(DrawingProgress progress);
UniTask SaveProgressAsync(DrawingProgress progress, byte[] thumbnailPng);
UniTask ClearProgressAsync(string templateId);
bool IsCompleted(string templateId);
UniTask<Texture2D> GetCachedThumbnailAsync(string templateId);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01c78ae114b2e4de08c195481bb84875

View File

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

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Darkmatter.Core.Enums.Features.Progression;
using UnityEngine;
namespace Darkmatter.Core.Data.Dynamic.Features.Progression
{
[Serializable]
public struct DrawingProgress
{
public string templateId;
public DrawingPhase phase;
public List<string> SnappedPieces;
public List<RegionColorEntry> RegionColors;
public bool hasThumbnail;
public bool hasBeenCompleted;
public int completionCount;
public DateTime UpdatedUtc;
public DateTime? FirstCompletedUtc;
public DrawingProgress(string templateId, DrawingPhase phase, List<string> snappedPieces,
List<RegionColorEntry> regionColors, bool hasThumbnail, bool hasBeenCompleted,
int completionCount, DateTime updatedUtc, DateTime? firstCompletedUtc)
{
this.templateId = templateId;
this.phase = phase;
SnappedPieces = snappedPieces;
RegionColors = regionColors;
this.hasThumbnail = hasThumbnail;
this.hasBeenCompleted = hasBeenCompleted;
this.completionCount = completionCount;
UpdatedUtc = updatedUtc;
FirstCompletedUtc = firstCompletedUtc;
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Darkmatter.Core.Data.Dynamic.Features.Progression
{
[Serializable]
public struct ProgressionRootDto
{
public List<DrawingProgress> records;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3c8f422a1719468387c90bdad95cf992
timeCreated: 1779887245

View File

@@ -0,0 +1,11 @@
using System;
namespace Darkmatter.Core.Data.Dynamic.Features.Progression
{
[Serializable]
public struct RegionColorEntry
{
public string regionId;
public float r, g, b, a;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 659d6e472d07425b86684258dda5644e
timeCreated: 1779886727

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using Darkmatter.Core.Contracts.Features.Coloring;
using UnityEngine;
namespace Darkmatter.Core.Data.Static.Features.Coloring
{
[CreateAssetMenu(menuName = "Darkmatter/Coloring/New Color Palette")]
public class ColorPaletteSO : ScriptableObject, IColorPalette
{
[field: SerializeField] public string Id { get; private set; }
[field: SerializeField] public List<Color> Colors { get; private set; }
}
}

View File

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

View File

@@ -1,9 +1,10 @@
using System.Collections.Generic;
using Darkmatter.Core.Contracts.Features.Drawing;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Data.Dynamic.Features.Coloring;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using UnityEngine;
namespace Darkmatter.Core.Data.Static.Features.Drawing
namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
{
[CreateAssetMenu(menuName = "Darkmatter/Drawing/New Drawing Template")]
public class DrawingTemplateSO : ScriptableObject, IDrawingTemplate

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 58192c2a0a784950851647d3f0130ad1
timeCreated: 1779882018

View File

@@ -1,6 +1,6 @@
using UnityEngine;
namespace Darkmatter.Core
namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
{
[CreateAssetMenu(fileName = "ShapeSO", menuName = "Darkmatter/Drawing/New Shape")]
public class ShapeSO : ScriptableObject

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
namespace Darkmatter.Core.Enums.Features.Progression
{
public enum DrawingPhase
{
ShapeBuilding,
Coloring,
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3ece528f850c24e0f8d3c33a732cc8c6

View File

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

View File

@@ -0,0 +1,20 @@
{
"name": "Features.DrawingCatalog",
"rootNamespace": "Darkmatter.Features.DrawingCatalog",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"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: eb9b7ee4936ff42bebd83ca110182103
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,21 @@
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Features.DrawingCatalog.Systems;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.DrawingCatalog.Installers
{
public class DrawingCatalogFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private DrawingCatalogView drawingCatalogView;
public void Register(IContainerBuilder builder)
{
builder.Register<IDrawingCatalogController, DrawingCatalogController>(Lifetime.Singleton);
if(drawingCatalogView != null)
builder.RegisterEntryPoint<DrawingCatalogPresenter>().WithParameter(drawingCatalogView);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Libs.Observer;
using ZLinq;
namespace Darkmatter.Features.DrawingCatalog.Systems;
public sealed class DrawingCatalogController : IDrawingCatalogController
{
private readonly IDrawingTemplateCatalog _catalog;
private readonly IEventBus _bus;
private readonly IProgressionSystem _progression;
private readonly List<string> _visible = new();
public IReadOnlyList<string> VisibleIds => _visible;
public event Action ListChanged;
public DrawingCatalogController(
IDrawingTemplateCatalog catalog,
IProgressionSystem progression,
IEventBus bus)
{
_catalog = catalog;
_progression = progression;
_bus = bus;
}
public async UniTask InitializeAsync(CancellationToken ct)
{
await _catalog.FetchAsync();
Refresh();
}
public void OnTemplateSelected(string id)
{
_bus.Publish(new DrawingSelectedSignal(id));
}
private void Refresh()
{
_visible.Clear();
var all = _catalog.AllTemplateIds;
foreach (var id in all)
if (!_progression.CompletedTemplateIds.AsValueEnumerable().Contains(id))
_visible.Add(id);
foreach (var id in all)
if (_progression.CompletedTemplateIds.AsValueEnumerable().Contains(id))
_visible.Add(id);
ListChanged?.Invoke();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1252b78470d594aa58188c32f552c52a

View File

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

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace Darkmatter.Features.DrawingCatalog;
public struct CatalogItemVM
{
public string Id { get; }
public Sprite Thumbnail { get; }
public CatalogItemVM(string id, Sprite thumbnail)
{
Id = id;
Thumbnail = thumbnail;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7b4274f9a3304610b13c6d01280139dc
timeCreated: 1779881383

View File

@@ -0,0 +1,18 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.DrawingCatalog;
public class DrawingCatalogButton : MonoBehaviour
{
public string Id { get; private set; }
[SerializeField] private Image thumbnail;
[SerializeField] private Button button;
public void Initialize(string id,Sprite thumbnailSprite, UnityEngine.Events.UnityAction onClick)
{
Id = id;
thumbnail.sprite = thumbnailSprite;
button.onClick.AddListener(onClick);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da23f33bd6fc4bf99f251714180406f1
timeCreated: 1779880745

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Features.DrawingCatalog.Systems;
using VContainer.Unity;
namespace Darkmatter.Features.DrawingCatalog
{
public class DrawingCatalogPresenter : IStartable, IDisposable
{
private readonly DrawingCatalogView _view;
private readonly IDrawingCatalogController _controller;
private readonly IDrawingTemplateCatalog _catalog;
private readonly CancellationTokenSource _cts = new();
public DrawingCatalogPresenter(DrawingCatalogView view, DrawingCatalogController controller,
IDrawingTemplateCatalog catalog)
{
_view = view;
_controller = controller;
_catalog = catalog;
}
public void Start()
{
_view.OnItemClicked += OnItemClicked;
_controller.ListChanged += OnListChanged;
}
private void OnItemClicked(string id) =>
_controller.OnTemplateSelected(id);
private void OnListChanged() =>
RefreshAsync(_cts.Token).Forget();
private async UniTask RefreshAsync(CancellationToken ct)
{
var ids = _controller.VisibleIds;
var vms = new List<CatalogItemVM>(ids.Count);
foreach (var id in ids)
{
if (ct.IsCancellationRequested) return;
var thumb = await _catalog.GetThumbnailAsync(id);
vms.Add(new CatalogItemVM(id, thumb));
}
if (ct.IsCancellationRequested) return;
_view.SetItems(vms);
}
public void Dispose()
{
_view.OnItemClicked -= OnItemClicked;
_controller.ListChanged -= OnListChanged;
_cts.Cancel();
_cts.Dispose();
}
}
}

View File

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

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
namespace Darkmatter.Features.DrawingCatalog
{
public class DrawingCatalogView : MonoBehaviour
{
[SerializeField] private RectTransform content;
[SerializeField] private DrawingCatalogButton buttonPrefab;
private readonly List<DrawingCatalogButton> _buttons = new();
public event Action<string> OnItemClicked;
public void SetItems(IReadOnlyList<CatalogItemVM> items)
{
while (_buttons.Count < items.Count)
{
var button = Instantiate(buttonPrefab, content);
var item = items[_buttons.Count];
button.Initialize(item.Id, item.Thumbnail,
() => { OnItemClicked?.Invoke(item.Id); });
_buttons.Add(button);
}
while (_buttons.Count > items.Count)
{
var last = _buttons[^1];
_buttons.RemoveAt(_buttons.Count - 1);
Destroy(last.gameObject);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 37f3d6f10bf1349a382f7ff96cc510b7

View File

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

View File

@@ -0,0 +1,20 @@
{
"name": "Features.DrawingTemplates",
"rootNamespace": "Darkmatter.Features.DrawingTemplates",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:efcaa22887a6b4471829c3b3878147a2",
"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: 6a005d98aed1c4439bc4689802fa2e3b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,16 @@
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Features.DrawingTemplates.Systems;
using UnityEngine;
using Darkmatter.Libs.Installers;
using VContainer;
namespace Darkmatter.Features.DrawingTemplates
{
public class DrawingTemplateFeatureModule : MonoBehaviour, IModule
{
public void Register(IContainerBuilder builder)
{
builder.Register<IDrawingTemplateCatalog, AddressableDrawingTemplateCatalog>(Lifetime.Singleton);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Data.Static.Features.DrawingTemplate;
using UnityEngine;
namespace Darkmatter.Features.DrawingTemplates.Systems
{
public sealed class AddressableDrawingTemplateCatalog : IDrawingTemplateCatalog
{
private const string DrawingLabel = "drawing";
private readonly IAssetProviderService _assets;
private readonly IProgressionSystem _progression;
private readonly List<string> _allIds = new();
private readonly Dictionary<string, DrawingTemplateSO> _byId = new();
private bool _initialized;
public IReadOnlyList<string> AllTemplateIds => _allIds;
public AddressableDrawingTemplateCatalog(
IAssetProviderService assets,
IProgressionSystem progression)
{
_assets = assets;
_progression = progression;
}
public async UniTask FetchAsync()
{
if (_initialized) return;
var templates = await _assets.LoadAssetsAsync<DrawingTemplateSO>(
key: DrawingLabel,
progress: null,
cancellationToken: CancellationToken.None);
_allIds.Clear();
_byId.Clear();
foreach (var t in templates)
{
if (t == null || string.IsNullOrEmpty(t.Id)) continue;
_allIds.Add(t.Id);
_byId[t.Id] = t;
}
_allIds.Sort();
_initialized = true;
}
public UniTask<Sprite> GetThumbnailAsync(string id)
{
if (!_byId.TryGetValue(id, out var t))
throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?");
return UniTask.FromResult(t.DefaultThumbnail);
}
public UniTask<IDrawingTemplate> LoadAsync(string id)
{
if (_byId.TryGetValue(id, out var t))
return UniTask.FromResult<IDrawingTemplate>(t);
return LoadIndividualAsync(id);
}
private async UniTask<IDrawingTemplate> LoadIndividualAsync(string id)
{
var t = await _assets.LoadAssetAsync<DrawingTemplateSO>(
id, progress: null, cancellationToken: CancellationToken.None);
if (t == null)
throw new KeyNotFoundException($"No drawing template at address '{id}'.");
_byId[id] = t;
if (!_allIds.Contains(id))
{
_allIds.Add(id);
_allIds.Sort();
}
return t;
}
public void ReleaseAll()
{
if (!_initialized) return;
_assets.UnloadAsset(DrawingLabel);
_byId.Clear();
_allIds.Clear();
_initialized = false;
}
public string GetNextTemplate(string currentId)
{
if (_allIds.Count == 0) return null;
int startIdx = currentId != null ? _allIds.IndexOf(currentId) : -1;
for (int i = 1; i <= _allIds.Count; i++)
{
var idx = (startIdx + i) % _allIds.Count;
var candidate = _allIds[idx];
if (!_progression.IsCompleted(candidate))
return candidate;
}
return _allIds[(startIdx + 1) % _allIds.Count];
}
}
}

View File

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

View File

@@ -6,7 +6,7 @@ using VContainer.Unity;
namespace Darkmatter.Features.History
{
public class HistoryServiceModule : MonoBehaviour, IServiceModule
public class HistoryFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private HistoryButtonsView historyButtonsView;

View File

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

View File

@@ -0,0 +1,20 @@
{
"name": "Features.Progression",
"rootNamespace": "Darkmatter.Features.Progression",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:564d11c0820a9455c8821cd85e9d0fd1",
"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: f0bb65a4e720b460289d630f2ebaf2d5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,17 @@
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Features.Progression.Systems;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
namespace Darkmatter.Features.Progression.Installers
{
public class ProgressionFeatureModule : MonoBehaviour, IModule
{
public void Register(IContainerBuilder builder)
{
builder.Register<ProgressionRepository>(Lifetime.Singleton);
builder.Register<IProgressionSystem, ProgressionSystem>(Lifetime.Singleton);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using Darkmatter.Libs.PlayerPrefs;
using UnityEngine;
namespace Darkmatter.Features.Progression.Systems;
public sealed class ProgressionRepository
{
private const string ThumbnailsFolder = "thumbnails";
private readonly Dictionary<string, DrawingProgress> _records = new();
public IEnumerable<DrawingProgress> AllProgress() => _records.Values;
public DrawingProgress? TryGet(string templateId) =>
_records.TryGetValue(templateId, out var p) ? p : (DrawingProgress?)null;
public UniTask LoadAsync()
{
_records.Clear();
var json = ProtectedPlayerPrefs.GetString(PlayerPrefsKeys.Progression, "");
if (string.IsNullOrEmpty(json)) return UniTask.CompletedTask;
ProgressionRootDto root;
try
{
root = JsonUtility.FromJson<ProgressionRootDto>(json);
}
catch (Exception e)
{
Debug.LogError($"[Progression] Failed to parse saved root: {e}");
return UniTask.CompletedTask;
}
if (root.records != null)
{
foreach (var rec in root.records)
if (!string.IsNullOrEmpty(rec.templateId))
_records[rec.templateId] = rec;
}
return UniTask.CompletedTask;
}
public UniTask SaveAsync()
{
FlushRoot();
return UniTask.CompletedTask;
}
public UniTask SaveProgressAsync(DrawingProgress p)
{
if (string.IsNullOrEmpty(p.templateId))
throw new ArgumentException("DrawingProgress.templateId must be set.");
_records[p.templateId] = p;
FlushRoot();
return UniTask.CompletedTask;
}
public UniTask DeleteProgressAsync(string templateId)
{
if (string.IsNullOrEmpty(templateId)) return UniTask.CompletedTask;
_records.Remove(templateId);
FlushRoot();
TryDeleteThumbnailFile(templateId);
return UniTask.CompletedTask;
}
public async UniTask WriteThumbnailAsync(string templateId, byte[] png)
{
if (string.IsNullOrEmpty(templateId) || png == null || png.Length == 0) return;
Directory.CreateDirectory(ThumbnailDirectory());
var path = ThumbnailPath(templateId);
var tmp = path + ".tmp";
await UniTask.RunOnThreadPool(() =>
{
File.WriteAllBytes(tmp, png);
if (File.Exists(path)) File.Delete(path);
File.Move(tmp, path);
});
}
public async UniTask<Texture2D> LoadThumbnailAsync(string templateId)
{
if (string.IsNullOrEmpty(templateId)) return null;
var path = ThumbnailPath(templateId);
if (!File.Exists(path)) return null;
byte[] bytes;
try
{
bytes = await UniTask.RunOnThreadPool(() => File.ReadAllBytes(path));
}
catch (Exception e)
{
Debug.LogWarning($"[Progression] Failed reading thumbnail '{templateId}': {e.Message}");
return null;
}
await UniTask.SwitchToMainThread();
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
if (!tex.LoadImage(bytes))
{
UnityEngine.Object.Destroy(tex);
return null;
}
return tex;
}
private void FlushRoot()
{
var root = new ProgressionRootDto
{
records = _records.Values.ToList(),
};
var json = JsonUtility.ToJson(root);
ProtectedPlayerPrefs.SetString(PlayerPrefsKeys.Progression, json);
ProtectedPlayerPrefs.Save();
}
private static void TryDeleteThumbnailFile(string templateId)
{
try
{
var path = ThumbnailPath(templateId);
if (File.Exists(path)) File.Delete(path);
}
catch (Exception e)
{
Debug.LogWarning($"[Progression] Failed deleting thumbnail '{templateId}': {e.Message}");
}
}
private static string ThumbnailDirectory() =>
Path.Combine(Application.persistentDataPath, ThumbnailsFolder);
private static string ThumbnailPath(string templateId) =>
Path.Combine(ThumbnailDirectory(), SafeFileName(templateId) + ".png");
private static string SafeFileName(string templateId) =>
templateId.Replace('/', '_').Replace('\\', '_');
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da8d941d4aa246b495da1fe0fe4a3c37
timeCreated: 1779883906

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using Darkmatter.Core.Enums.Features.Progression;
using Darkmatter.Features.Progression.Systems;
using UnityEngine;
public class ProgressionSystem : IProgressionSystem
{
private readonly ProgressionRepository _repository;
private readonly Dictionary<string, DrawingProgress> _records = new();
private readonly HashSet<string> _completed = new();
private readonly SemaphoreSlim _writeLock = new(1, 1);
public IReadOnlyCollection<string> CompletedTemplateIds => _completed;
public ProgressionSystem(ProgressionRepository repository)
{
_repository = repository;
}
public async UniTask LoadAsync()
{
await _repository.LoadAsync();
_records.Clear();
_completed.Clear();
foreach (var p in _repository.AllProgress())
{
_records[p.templateId] = p;
if (p.hasBeenCompleted) _completed.Add(p.templateId);
}
}
public UniTask SaveAsync() => _repository.SaveAsync();
public void MarkCompleted(string templateId)
{
if (!_records.TryGetValue(templateId, out var p))
{
p = new DrawingProgress { templateId = templateId, phase = DrawingPhase.Coloring };
}
p.hasBeenCompleted = true;
p.completionCount += 1;
_records[templateId] = p;
_completed.Add(templateId);
}
public DrawingProgress? GetProgress(string templateId) =>
_records.TryGetValue(templateId, out var p) ? p : (DrawingProgress?)null;
public UniTask SaveProgressAsync(DrawingProgress progress) =>
SaveProgressAsync(progress, thumbnailPng: null);
public async UniTask SaveProgressAsync(DrawingProgress progress, byte[] thumbnailPng)
{
await _writeLock.WaitAsync();
try
{
_records[progress.templateId] = progress;
if (progress.hasBeenCompleted) _completed.Add(progress.templateId);
await _repository.SaveProgressAsync(progress);
if (thumbnailPng != null)
await _repository.WriteThumbnailAsync(progress.templateId, thumbnailPng);
}
finally
{
_writeLock.Release();
}
}
public async UniTask ClearProgressAsync(string templateId)
{
await _writeLock.WaitAsync();
try
{
_records.Remove(templateId);
_completed.Remove(templateId);
await _repository.DeleteProgressAsync(templateId);
}
finally
{
_writeLock.Release();
}
}
public bool IsCompleted(string templateId) => _completed.Contains(templateId);
public UniTask<Texture2D> GetCachedThumbnailAsync(string templateId) =>
_repository.LoadThumbnailAsync(templateId);
}

View File

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

View File

@@ -2,7 +2,7 @@ using VContainer;
namespace Darkmatter.Libs.Installers
{
public interface IServiceModule
public interface IModule
{
void Register(IContainerBuilder builder);
}

View File

@@ -7,45 +7,7 @@ namespace Darkmatter.Libs.PlayerPrefs
{
public static class PlayerPrefsKeys
{
/// <summary>Saves the achievements which the user has unlocked (String)</summary>
public const string Achievements = "Achievements";
public const string LocalLedger = "LocalLedger";
public static class Accounts
{
public const string SavedAuthRequest = "Accounts.SavedAuthRequest";
}
public static class Economy
{
/// <summary>Saves user's hard Currency (Int)</summary>
public const string Gold = "Economy.Gold";
/// <summary>Saves User's soft currency (Int)</summary>
public const string Rupees = "Economy.Rupees";
}
public static class Garage
{
/// <summary>Json of ids of the user owned buses (String)</summary>
public const string OwnedBusIds = "Garage.OwnedBusIds";
/// <summary>Id of the Bus that the user has selected (String)</summary>
public const string SelectedBusId = "Garage.SelectedBusId";
}
public static class Progression
{
/// <summary>Saves the user's Level (Int)</summary>
public const string Level = "Progression.Level";
/// <summary>Saves Xp of the user (Int)</summary>
public const string Xp = "Progression.Xp";
}
public static class SaveGame
{
/// <summary>Saves the users session 's json (String)</summary>
public const string Session = "SaveGame.Session";
public const string Vehicle = "SaveGame.Vehicle";
}
/// <summary>Stores User's Progression Data (String)</summary>
public const string Progression = "Progression";
}
}

View File

@@ -5,7 +5,7 @@ using VContainer.Unity;
namespace Darkmatter.Services.Analytics
{
public class AnalyticsServiceModule : MonoBehaviour, IServiceModule
public class AnalyticsModule : MonoBehaviour, IModule
{
public void Register(IContainerBuilder builder)
{

View File

@@ -7,7 +7,7 @@ using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType;
namespace Darkmatter.Services.Camera.Installers
{
public class CameraServiceModule : MonoBehaviour, IServiceModule
public class CameraModule : MonoBehaviour, IModule
{
[SerializeField] private UnityEngine.Camera mainCamera;
[SerializeField] private UnityEngine.Camera uiCamera;

View File

@@ -5,7 +5,7 @@ using VContainer;
namespace Darkmatter.Services.Capture.Installers
{
public class CaptureServiceModule : MonoBehaviour, IServiceModule
public class CaptureModule : MonoBehaviour, IModule
{
public void Register(IContainerBuilder builder)
{

View File

@@ -5,7 +5,7 @@ using VContainer;
namespace Darkmatter.Services.Gallery
{
public class GalleryServiceModule : MonoBehaviour,IServiceModule
public class GalleryModule : MonoBehaviour,IModule
{
public void Register(IContainerBuilder builder)
{