image save/load
This commit is contained in:
@@ -17,16 +17,21 @@ namespace Darkmatter.Features.AppBoot.Flow
|
||||
private readonly AppBootSceneRefs _sceneRefs;
|
||||
private readonly ISceneService _sceneService;
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly IProgressionSystem _progression;
|
||||
|
||||
public AppBootFlow(AppBootSceneRefs sceneRefs, ISceneService sceneService, IEventBus eventBus)
|
||||
public AppBootFlow(AppBootSceneRefs sceneRefs, ISceneService sceneService, IEventBus eventBus,
|
||||
IProgressionSystem progression)
|
||||
{
|
||||
_sceneRefs = sceneRefs;
|
||||
_sceneService = sceneService;
|
||||
_eventBus = eventBus;
|
||||
_progression = progression;
|
||||
}
|
||||
|
||||
public async UniTask StartAsync(CancellationToken cancellation = default)
|
||||
{
|
||||
await _progression.LoadAsync();
|
||||
|
||||
var tcs = new UniTaskCompletionSource();
|
||||
var player = _sceneRefs.IntroVideoPlayer;
|
||||
|
||||
|
||||
8
Assets/Darkmatter/Code/Features/Capture.meta
Normal file
8
Assets/Darkmatter/Code/Features/Capture.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eeefc3c8ab31d4ac983deab507c76b1f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Features.Capture",
|
||||
"rootNamespace": "Darkmatter.Features.Capture",
|
||||
"references": [
|
||||
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80056ede5198b460198933cb79d694ff
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Darkmatter/Code/Features/Capture/Installers.meta
Normal file
8
Assets/Darkmatter/Code/Features/Capture/Installers.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 338d273a95ef0403ca2dd1aca67f97f5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using Darkmatter.Features.Capture.UI;
|
||||
using Darkmatter.Libs.Installers;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace Darkmatter.Features.Capture
|
||||
{
|
||||
public class CaptureFeatureModule : MonoBehaviour, IModule
|
||||
{
|
||||
[SerializeField, Range(0.1f, 2f)] private float captureScale = 1f;
|
||||
[SerializeField] private CaptureButtonView captureButtonView;
|
||||
|
||||
public void Register(IContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterInstance(new CaptureConfig(captureScale));
|
||||
builder.Register<ICaptureFeature, CaptureSystem>(Lifetime.Singleton);
|
||||
|
||||
if (captureButtonView != null)
|
||||
builder.RegisterEntryPoint<CaptureButtonPresenter>().WithParameter(captureButtonView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b7df1c2a732341d3b123b5e7ae5d7b7
|
||||
8
Assets/Darkmatter/Code/Features/Capture/Systems.meta
Normal file
8
Assets/Darkmatter/Code/Features/Capture/Systems.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90fa308dc501c46bdb9488e2d6d3e034
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Darkmatter.Features.Capture
|
||||
{
|
||||
[Serializable]
|
||||
public struct CaptureConfig
|
||||
{
|
||||
public float CaptureScale { get; }
|
||||
|
||||
public CaptureConfig(float captureScale)
|
||||
{
|
||||
CaptureScale = captureScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f12070b2d795429a9b7f6cc0e4ae894
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using Darkmatter.Core.Contracts.Features.GameplayFlow;
|
||||
using Darkmatter.Core.Contracts.Services.Capture;
|
||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Features.Capture
|
||||
{
|
||||
public class CaptureSystem : ICaptureFeature
|
||||
{
|
||||
private readonly ICaptureService _captureService;
|
||||
private readonly IGalleryService _galleryService;
|
||||
private readonly IGameplaySceneRefs _refs;
|
||||
private readonly CaptureConfig _config;
|
||||
|
||||
public CaptureSystem(
|
||||
ICaptureService captureService,
|
||||
IGalleryService galleryService,
|
||||
IGameplaySceneRefs refs,
|
||||
CaptureConfig config)
|
||||
{
|
||||
_captureService = captureService;
|
||||
_galleryService = galleryService;
|
||||
_refs = refs;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async UniTask<byte[]> CapturePngAsync(bool saveToGallery = false, CancellationToken ct = default)
|
||||
{
|
||||
var png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct);
|
||||
if (!saveToGallery || png == null || png.Length == 0) return png;
|
||||
|
||||
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
|
||||
try
|
||||
{
|
||||
if (tex.LoadImage(png))
|
||||
await _galleryService.SaveImageAsync(tex,
|
||||
$"colorbook_{DateTime.UtcNow:yyyyMMdd_HHmmss}.png", ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.Destroy(tex);
|
||||
}
|
||||
return png;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76bbd68f070e24d6a86130ec07c237b8
|
||||
8
Assets/Darkmatter/Code/Features/Capture/UI.meta
Normal file
8
Assets/Darkmatter/Code/Features/Capture/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88715eafa42924498a994a891b15c9dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace Darkmatter.Features.Capture.UI
|
||||
{
|
||||
public class CaptureButtonPresenter : IStartable, IDisposable
|
||||
{
|
||||
private readonly CaptureButtonView _view;
|
||||
private readonly ICaptureFeature _capture;
|
||||
|
||||
public CaptureButtonPresenter(CaptureButtonView view, ICaptureFeature capture)
|
||||
{
|
||||
_view = view;
|
||||
_capture = capture;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_view.OnCaptureClicked += HandleCaptureClicked;
|
||||
}
|
||||
|
||||
private void HandleCaptureClicked() => CaptureAsync().Forget();
|
||||
|
||||
private async UniTaskVoid CaptureAsync()
|
||||
{
|
||||
_view.SetInteractable(false);
|
||||
try
|
||||
{
|
||||
await _capture.CapturePngAsync(saveToGallery: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_view.SetInteractable(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_view.OnCaptureClicked -= HandleCaptureClicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec4fcbab1dbeb4d1690801eee2cc5940
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Darkmatter.Features.Capture.UI
|
||||
{
|
||||
public class CaptureButtonView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Button captureButton;
|
||||
|
||||
public event Action OnCaptureClicked;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (captureButton != null)
|
||||
captureButton.onClick.AddListener(() => OnCaptureClicked?.Invoke());
|
||||
}
|
||||
|
||||
public void SetInteractable(bool value)
|
||||
{
|
||||
if (captureButton != null) captureButton.interactable = value;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (captureButton != null) captureButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f0946b153da9431eb6c66bda432901f
|
||||
@@ -27,6 +27,7 @@ public class ColoringController : IColoringController, IDisposable
|
||||
private readonly IAssetProviderService _assetProviderService;
|
||||
private readonly IUndoStack _history;
|
||||
private readonly IGameplaySceneRefs _refs;
|
||||
private readonly ColorPaletteHolderView _paletteHolder;
|
||||
|
||||
private GameObject _colorInstance;
|
||||
private GameObject _colorButtonPrefab;
|
||||
@@ -39,7 +40,8 @@ public class ColoringController : IColoringController, IDisposable
|
||||
IEventBus bus,
|
||||
IAssetProviderService assetProviderService,
|
||||
IUndoStack history,
|
||||
IGameplaySceneRefs refs)
|
||||
IGameplaySceneRefs refs,
|
||||
ColorPaletteHolderView paletteHolder)
|
||||
{
|
||||
_repository = repository;
|
||||
_buttonFactory = buttonFactory;
|
||||
@@ -47,12 +49,14 @@ public class ColoringController : IColoringController, IDisposable
|
||||
_assetProviderService = assetProviderService;
|
||||
_history = history;
|
||||
_refs = refs;
|
||||
_paletteHolder = paletteHolder;
|
||||
}
|
||||
|
||||
public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
|
||||
IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct)
|
||||
{
|
||||
Clear();
|
||||
_paletteHolder.Show();
|
||||
await TryLoadColorButtonPrefabAsync(ct);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
await TryLoadColorPaletteAsync(template, ct);
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Darkmatter.Features.Coloring.UI
|
||||
_color = color;
|
||||
_repository = repository;
|
||||
_sfx = sfx;
|
||||
if (_button == null) _button = GetComponent<Button>();
|
||||
foreach (var swatch in swatches)
|
||||
{
|
||||
swatch.color = color;
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Darkmatter.Features.Coloring.UI
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
Debug.Log($"[ColorRegion] clicked '{RegionId}' on '{gameObject.name}'");
|
||||
OnRegionClicked?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,18 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public UniTask<Sprite> GetThumbnailAsync(string id)
|
||||
public async 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);
|
||||
|
||||
var savedTex = await _progression.GetCachedThumbnailAsync(id);
|
||||
if (savedTex != null)
|
||||
{
|
||||
var rect = new Rect(0, 0, savedTex.width, savedTex.height);
|
||||
return Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
return t.DefaultThumbnail;
|
||||
}
|
||||
|
||||
public UniTask<IDrawingTemplate> LoadAsync(string id)
|
||||
|
||||
@@ -2,13 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using Darkmatter.Core.Contracts.Features.Coloring;
|
||||
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||
using Darkmatter.Core.Contracts.Features.GameplayFlow;
|
||||
using Darkmatter.Core.Contracts.Features.Loading;
|
||||
using Darkmatter.Core.Contracts.Features.Progression;
|
||||
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
|
||||
using Darkmatter.Core.Contracts.Services.Capture;
|
||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
||||
using Darkmatter.Core.Contracts.Services.Scenes;
|
||||
using Darkmatter.Core.Data.Dynamic.Features.Progression;
|
||||
@@ -31,8 +31,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
private readonly IShapeBuilderController _shapeBuilder;
|
||||
private readonly IColoringController _coloring;
|
||||
private readonly ISceneService _scenes;
|
||||
private readonly ICaptureService _captureService;
|
||||
private readonly IGameplaySceneRefs _refs;
|
||||
private readonly ICaptureFeature _capture;
|
||||
private readonly ILoadingScreen _loadingScreen;
|
||||
private readonly IEventBus _bus;
|
||||
|
||||
@@ -51,8 +50,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
IShapeBuilderController shapeBuilder,
|
||||
IColoringController coloring,
|
||||
ISceneService scenes,
|
||||
ICaptureService captureService,
|
||||
IGameplaySceneRefs refs,
|
||||
ICaptureFeature capture,
|
||||
ILoadingScreen loadingScreen,
|
||||
IEventBus bus)
|
||||
{
|
||||
@@ -61,9 +59,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_shapeBuilder = shapeBuilder;
|
||||
_coloring = coloring;
|
||||
_scenes = scenes;
|
||||
_captureService = captureService;
|
||||
_capture = capture;
|
||||
_loadingScreen = loadingScreen;
|
||||
_refs = refs;
|
||||
_bus = bus;
|
||||
}
|
||||
|
||||
@@ -89,6 +86,9 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
|
||||
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
|
||||
|
||||
Application.quitting += OnAppQuitting;
|
||||
Application.focusChanged += OnAppFocusChanged;
|
||||
|
||||
_loadingScreen.SetProgress(1f);
|
||||
if (_phase == DrawingPhase.Coloring)
|
||||
{
|
||||
@@ -105,7 +105,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
|
||||
public async UniTask BackAsync(CancellationToken ct)
|
||||
{
|
||||
await SaveCurrentAsync(captureThumbnail: true, CancellationToken.None);
|
||||
await SaveCurrentAsync(CancellationToken.None);
|
||||
_shapeBuilder.Clear();
|
||||
_coloring.Clear();
|
||||
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: ct);
|
||||
@@ -113,14 +113,13 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
|
||||
public async UniTask SaveAsync(CancellationToken ct)
|
||||
{
|
||||
// Explicit user-pressed save — capture thumbnail + (planned) native gallery export.
|
||||
await SaveCurrentAsync(captureThumbnail: true, ct);
|
||||
// TODO: route through ICaptureService + IGalleryService once those exist.
|
||||
await SaveCurrentAsync(ct);
|
||||
await _capture.CapturePngAsync(saveToGallery: true, ct);
|
||||
}
|
||||
|
||||
public async UniTask NextAsync(CancellationToken ct)
|
||||
{
|
||||
await SaveCurrentAsync(captureThumbnail: true, ct);
|
||||
await SaveCurrentAsync(ct);
|
||||
_progression.MarkCompleted(_templateId);
|
||||
|
||||
var nextId = _catalog.GetNextTemplate(_templateId);
|
||||
@@ -139,7 +138,14 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
public void OnApplicationPaused()
|
||||
{
|
||||
// Fire-and-forget — pause window is small; PlayerPrefs write is sync under the hood.
|
||||
SaveCurrentAsync(captureThumbnail: false, CancellationToken.None).Forget();
|
||||
SaveCurrentAsync(CancellationToken.None).Forget();
|
||||
}
|
||||
|
||||
private void OnAppQuitting() => OnApplicationPaused();
|
||||
|
||||
private void OnAppFocusChanged(bool focused)
|
||||
{
|
||||
if (!focused) OnApplicationPaused();
|
||||
}
|
||||
|
||||
private void OnShapeAssembled(ShapeAssembledSignal signal)
|
||||
@@ -157,7 +163,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
await _coloring.InitializeRegionsAsync(_template, savedColors, _scopeCts.Token);
|
||||
_shapeBuilder.DespawnDrawing();
|
||||
|
||||
await SaveCurrentAsync(captureThumbnail: true, ct);
|
||||
await SaveCurrentAsync(ct);
|
||||
}
|
||||
|
||||
private void OnColorApplied(ColorAppliedSignal _)
|
||||
@@ -174,7 +180,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
try
|
||||
{
|
||||
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
|
||||
await SaveCurrentAsync(captureThumbnail: false, ct);
|
||||
await SaveCurrentAsync(ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -182,7 +188,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SaveCurrentAsync(bool captureThumbnail, CancellationToken ct)
|
||||
private async UniTask SaveCurrentAsync(CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateId)) return;
|
||||
|
||||
@@ -198,18 +204,14 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
phase = _phase,
|
||||
snappedPieces = snappedIds,
|
||||
regionColors = regionEntries,
|
||||
hasThumbnail = captureThumbnail || (existing?.hasThumbnail ?? false),
|
||||
hasThumbnail = true,
|
||||
hasBeenCompleted = existing?.hasBeenCompleted ?? false,
|
||||
completionCount = existing?.completionCount ?? 0,
|
||||
UpdatedUtc = DateTime.UtcNow,
|
||||
FirstCompletedUtc = existing?.FirstCompletedUtc,
|
||||
};
|
||||
|
||||
byte[] thumbnailPng = null;
|
||||
if (captureThumbnail)
|
||||
{
|
||||
thumbnailPng = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, 10, ct);
|
||||
}
|
||||
var thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
|
||||
|
||||
await _progression.SaveProgressAsync(progress, thumbnailPng);
|
||||
}
|
||||
@@ -226,6 +228,9 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Application.quitting -= OnAppQuitting;
|
||||
Application.focusChanged -= OnAppFocusChanged;
|
||||
|
||||
_assembledSub?.Dispose();
|
||||
_colorAppliedSub?.Dispose();
|
||||
_autosaveCts?.Cancel();
|
||||
|
||||
Reference in New Issue
Block a user