From a02b70e1f7f4d1b5d24db1cde7bcc7e10acd8ce5 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Tue, 25 Nov 2025 15:48:18 +0100 Subject: [PATCH] Fix method signatures for apple machines --- .../Scenes/MiniGames/StatueDecoration.unity | 48 +++ Assets/Scripts/AppleHillsScripts.asmdef | 4 +- Assets/Scripts/Core/SaveLoad/AppleMachine.cs | 14 +- .../Controllers/PhotoCaptureTestController.cs | 93 ++++ .../PhotoCaptureTestController.cs.meta | 3 + .../Controllers/PhotoGridItem.cs | 79 ++++ .../Controllers/PhotoGridItem.cs.meta | 3 + .../Controllers/StatueDecorationController.cs | 113 ++--- .../StatuePhotoGalleryController.cs | 394 +++++++++++++++++ .../StatuePhotoGalleryController.cs.meta | 3 + .../StatueDressup/Utils/StatuePhotoManager.cs | 399 ++++++++++++++++++ .../Utils/StatuePhotoManager.cs.meta | 2 + 12 files changed, 1077 insertions(+), 78 deletions(-) create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs.meta create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs.meta create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs create mode 100644 Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs.meta create mode 100644 Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs create mode 100644 Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs.meta diff --git a/Assets/Scenes/MiniGames/StatueDecoration.unity b/Assets/Scenes/MiniGames/StatueDecoration.unity index 07df1172..d0a924dd 100644 --- a/Assets/Scenes/MiniGames/StatueDecoration.unity +++ b/Assets/Scenes/MiniGames/StatueDecoration.unity @@ -1560,6 +1560,53 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1685271989 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1685271991} + - component: {fileID: 1685271990} + m_Layer: 0 + m_Name: TestController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1685271990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1685271989} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: deab1758ddef4bdea0e2c50554eaf568, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.PhotoCaptureTestController + captureArea: {fileID: 65358845} + captureButton: {fileID: 37633367} + hideTheseObjects: [] +--- !u!4 &1685271991 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1685271989} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -29.79184, y: 0.56528, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &2071711337 GameObject: m_ObjectHideFlags: 0 @@ -1691,3 +1738,4 @@ SceneRoots: - {fileID: 1217454518} - {fileID: 1126329096} - {fileID: 483064112} + - {fileID: 1685271991} diff --git a/Assets/Scripts/AppleHillsScripts.asmdef b/Assets/Scripts/AppleHillsScripts.asmdef index 2de6f9e7..07854c9f 100644 --- a/Assets/Scripts/AppleHillsScripts.asmdef +++ b/Assets/Scripts/AppleHillsScripts.asmdef @@ -11,7 +11,9 @@ "OptimizedRope", "AudioSourceEvents", "NewAssembly", - "Unity.Cinemachine" + "Unity.Cinemachine", + "ScreenshotHelper", + "SwanDevCommon" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/Scripts/Core/SaveLoad/AppleMachine.cs b/Assets/Scripts/Core/SaveLoad/AppleMachine.cs index 6859cdd3..dabcd5ce 100644 --- a/Assets/Scripts/Core/SaveLoad/AppleMachine.cs +++ b/Assets/Scripts/Core/SaveLoad/AppleMachine.cs @@ -26,9 +26,9 @@ namespace Core.SaveLoad /// Has this state machine been restored from save data? /// public bool HasBeenRestored { get; private set; } - + // Override ChangeState to call OnEnterState on SaveableState components - public new GameObject ChangeState(GameObject state) + public new void ChangeState(GameObject state) { var result = base.ChangeState(state); @@ -41,11 +41,9 @@ namespace Core.SaveLoad saveableState.OnEnterState(); } } - - return result; } - public new GameObject ChangeState(string state) + public new void ChangeState(string state) { var result = base.ChangeState(state); @@ -58,11 +56,9 @@ namespace Core.SaveLoad saveableState.OnEnterState(); } } - - return result; } - public new GameObject ChangeState(int childIndex) + public new void ChangeState(int childIndex) { var result = base.ChangeState(childIndex); @@ -75,8 +71,6 @@ namespace Core.SaveLoad saveableState.OnEnterState(); } } - - return result; } private void Start() diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs new file mode 100644 index 00000000..9f177755 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs @@ -0,0 +1,93 @@ +using Core; +using Minigames.StatueDressup.Utils; +using UnityEngine; +using UnityEngine.UI; + +namespace Minigames.StatueDressup.Controllers +{ + /// + /// Minimal test for photo capture - just capture and save to disk + /// + public class PhotoCaptureTestController : MonoBehaviour + { + [Header("Required")] + [SerializeField] private RectTransform captureArea; + [SerializeField] private Button captureButton; + + [Header("Optional - UI to hide during capture")] + [SerializeField] private GameObject[] hideTheseObjects; + + private void Start() + { + if (captureButton != null) + { + captureButton.onClick.AddListener(OnCaptureClicked); + } + + Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {StatuePhotoManager.GetPhotoDirectory()}"); + } + + private void OnCaptureClicked() + { + if (captureArea == null) + { + Debug.LogError("[PhotoCaptureTest] Capture Area not assigned!"); + return; + } + + Debug.Log("[PhotoCaptureTest] Starting capture..."); + StartCoroutine(CaptureCoroutine()); + } + + private System.Collections.IEnumerator CaptureCoroutine() + { + // Hide UI + foreach (var obj in hideTheseObjects) + if (obj != null) obj.SetActive(false); + + yield return new WaitForEndOfFrame(); + + // Capture + bool done = false; + Texture2D photo = null; + + StatuePhotoManager.CaptureAreaPhoto(captureArea, (texture) => { + photo = texture; + done = true; + }); + + yield return new WaitUntil(() => done); + + // Restore UI + foreach (var obj in hideTheseObjects) + if (obj != null) obj.SetActive(true); + + // Save + if (photo != null) + { + string photoId = StatuePhotoManager.SavePhoto(photo, 0); + + if (!string.IsNullOrEmpty(photoId)) + { + string path = $"{StatuePhotoManager.GetPhotoDirectory()}/{photoId}.png"; + Debug.Log($"[PhotoCaptureTest] ✅ SUCCESS! Photo saved: {path}"); + Debug.Log($"[PhotoCaptureTest] Photo size: {photo.width}x{photo.height}"); + } + else + { + Debug.LogError("[PhotoCaptureTest] ❌ Failed to save photo"); + } + } + else + { + Debug.LogError("[PhotoCaptureTest] ❌ Failed to capture photo"); + } + } + + private void OnDestroy() + { + if (captureButton != null) + captureButton.onClick.RemoveListener(OnCaptureClicked); + } + } +} diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs.meta new file mode 100644 index 00000000..26f394e5 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoCaptureTestController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: deab1758ddef4bdea0e2c50554eaf568 +timeCreated: 1764077035 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs new file mode 100644 index 00000000..240ede7a --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs @@ -0,0 +1,79 @@ +using Core; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.EventSystems; + +namespace Minigames.StatueDressup.Controllers +{ + /// + /// Individual photo thumbnail in the gallery grid. + /// Handles click to show enlarged view. + /// + public class PhotoGridItem : MonoBehaviour, IPointerClickHandler + { + [Header("References")] + [SerializeField] private Image thumbnailImage; + [SerializeField] private GameObject loadingIndicator; + + private string _photoId; + private StatuePhotoGalleryController _galleryController; + + /// + /// Initialize grid item with photo ID + /// + public void Initialize(string photoId, StatuePhotoGalleryController controller) + { + _photoId = photoId; + _galleryController = controller; + + // Show loading state + if (loadingIndicator != null) + loadingIndicator.SetActive(true); + + if (thumbnailImage != null) + thumbnailImage.enabled = false; + } + + /// + /// Set the thumbnail texture + /// + public void SetThumbnail(Texture2D thumbnail) + { + if (thumbnail == null) + { + Logging.Warning($"[PhotoGridItem] Null thumbnail for photo: {_photoId}"); + return; + } + + // Create sprite from thumbnail + Sprite thumbnailSprite = Sprite.Create( + thumbnail, + new Rect(0, 0, thumbnail.width, thumbnail.height), + new Vector2(0.5f, 0.5f) + ); + + if (thumbnailImage != null) + { + thumbnailImage.sprite = thumbnailSprite; + thumbnailImage.enabled = true; + } + + // Hide loading indicator + if (loadingIndicator != null) + loadingIndicator.SetActive(false); + } + + /// + /// Handle click to show enlarged view + /// + public void OnPointerClick(PointerEventData eventData) + { + if (_galleryController != null && !string.IsNullOrEmpty(_photoId)) + { + Logging.Debug($"[PhotoGridItem] Clicked: {_photoId}"); + _galleryController.ShowEnlargedView(_photoId); + } + } + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs.meta new file mode 100644 index 00000000..a02c349b --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/PhotoGridItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: acd8d97ee2f744d984a9507e75309be0 +timeCreated: 1764065014 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs index abd31db8..fd624a9a 100644 --- a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatueDecorationController.cs @@ -129,82 +129,61 @@ namespace Minigames.StatueDressup.Controllers { yield return new WaitForEndOfFrame(); - // Capture the photo area - Texture2D photo = CaptureScreenshotArea(); + // Capture using Screenshot Helper via StatuePhotoManager + bool captureComplete = false; + Texture2D capturedPhoto = null; - if (photo != null) + Utils.StatuePhotoManager.CaptureAreaPhoto( + photoArea, + (Texture2D texture) => + { + capturedPhoto = texture; + captureComplete = true; + }, + Camera.main + ); + + // Wait for capture to complete + yield return new WaitUntil(() => captureComplete); + + if (capturedPhoto != null) { - // Save photo to album - SavePhotoToAlbum(photo); + // Save photo with StatuePhotoManager + int decorationCount = _placedDecorations.Count; + string photoId = Utils.StatuePhotoManager.SavePhoto(capturedPhoto, decorationCount); - // Award cards - AwardCards(); - - // Update town icon - UpdateTownIcon(photo); - - // Show completion feedback - ShowCompletionFeedback(); - - _minigameCompleted = true; + if (!string.IsNullOrEmpty(photoId)) + { + Logging.Debug($"[StatueDecorationController] Photo saved: {photoId}"); + + // Award cards + AwardCards(); + + // Update town icon + UpdateTownIcon(capturedPhoto); + + // Show completion feedback + ShowCompletionFeedback(); + + _minigameCompleted = true; + } + else + { + Logging.Error("[StatueDecorationController] Failed to save photo!"); + DebugUIMessage.Show("Failed to save photo!", Color.red); + } + } + else + { + Logging.Error("[StatueDecorationController] Failed to capture photo!"); + DebugUIMessage.Show("Failed to capture photo!", Color.red); } // Restore UI HideUIForPhoto(false); } - /// - /// Capture screenshot of specific area - /// - private Texture2D CaptureScreenshotArea() - { - if (photoArea == null) - { - Logging.Warning("[StatueDecorationController] No photo area specified, capturing full screen"); - - // Capture full screen - Texture2D screenshot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false); - screenshot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0); - screenshot.Apply(); - return screenshot; - } - - // Get world corners of the rect - Vector3[] corners = new Vector3[4]; - photoArea.GetWorldCorners(corners); - - // Convert to screen space - Vector2 min = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[0]); - Vector2 max = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[2]); - - int width = (int)(max.x - min.x); - int height = (int)(max.y - min.y); - - Logging.Debug($"[StatueDecorationController] Capturing area: {width}x{height} at ({min.x}, {min.y})"); - - // Capture the specified area - Texture2D areaScreenshot = new Texture2D(width, height, TextureFormat.RGB24, false); - areaScreenshot.ReadPixels(new Rect(min.x, min.y, width, height), 0, 0); - areaScreenshot.Apply(); - - return areaScreenshot; - } - - /// - /// Save photo to card album - /// - private void SavePhotoToAlbum(Texture2D photo) - { - // TODO: Integrate with existing album save system - // For now, save to PlayerPrefs as base64 - byte[] bytes = photo.EncodeToPNG(); - string base64 = System.Convert.ToBase64String(bytes); - string saveKey = _settings?.PhotoSaveKey ?? photoSaveKey; - PlayerPrefs.SetString(saveKey, base64); - PlayerPrefs.Save(); - - Logging.Debug("[StatueDecorationController] Photo saved to album"); - } + /// /// Award Blokkemon cards to player diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs new file mode 100644 index 00000000..271093e3 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs @@ -0,0 +1,394 @@ +using System.Collections; +using System.Collections.Generic; +using Core; +using Core.Lifecycle; +using Minigames.StatueDressup.Utils; +using UnityEngine; +using UnityEngine.UI; + +namespace Minigames.StatueDressup.Controllers +{ + /// + /// Manages photo gallery display with optimized memory usage. + /// Loads photos in pages and caches thumbnails to avoid loading 1000+ photos at once. + /// Supports grid view with thumbnail preview and enlarged view on selection. + /// + public class StatuePhotoGalleryController : ManagedBehaviour + { + [Header("Gallery UI")] + [SerializeField] private Transform gridContainer; + [SerializeField] private PhotoGridItem gridItemPrefab; + [SerializeField] private ScrollRect scrollRect; + + [Header("Enlarged View")] + [SerializeField] private GameObject enlargedViewPanel; + [SerializeField] private Image enlargedPhotoImage; + [SerializeField] private Button closeEnlargedButton; + [SerializeField] private Button deletePhotoButton; + [SerializeField] private Text photoInfoText; + + [Header("Pagination")] + [SerializeField] private Button loadMoreButton; + [SerializeField] private Text statusText; + + [Header("Settings")] + [SerializeField] private int itemsPerPage = 20; + [SerializeField] private int thumbnailSize = 256; + [SerializeField] private int maxCachedThumbnails = 50; // Keep recent thumbnails in memory + + private int _currentPage = 0; + private List _allPhotoIds = new List(); + private Dictionary _activeGridItems = new Dictionary(); + private Dictionary _thumbnailCache = new Dictionary(); + private Queue _thumbnailCacheOrder = new Queue(); + private string _currentEnlargedPhotoId = null; + private Texture2D _currentEnlargedTexture = null; + + internal override void OnManagedStart() + { + base.OnManagedStart(); + + // Setup buttons + if (closeEnlargedButton != null) + closeEnlargedButton.onClick.AddListener(CloseEnlargedView); + + if (deletePhotoButton != null) + deletePhotoButton.onClick.AddListener(DeleteCurrentPhoto); + + if (loadMoreButton != null) + loadMoreButton.onClick.AddListener(LoadNextPage); + + // Hide enlarged view initially + if (enlargedViewPanel != null) + enlargedViewPanel.SetActive(false); + + // Load first page + RefreshGallery(); + } + + /// + /// Refresh the entire gallery from scratch + /// + public void RefreshGallery() + { + // Clear existing items + ClearGallery(); + + // Get all photo IDs + _allPhotoIds = StatuePhotoManager.GetAllPhotoIds(); + _currentPage = 0; + + Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {_allPhotoIds.Count} photos"); + + // Load first page + LoadNextPage(); + } + + /// + /// Load next page of photos + /// + private void LoadNextPage() + { + List pagePhotoIds = StatuePhotoManager.GetPhotoIdsPage(_currentPage, itemsPerPage); + + if (pagePhotoIds.Count == 0) + { + if (loadMoreButton != null) + loadMoreButton.gameObject.SetActive(false); + + UpdateStatusText($"All photos loaded ({_allPhotoIds.Count} total)"); + return; + } + + Logging.Debug($"[StatuePhotoGalleryController] Loading page {_currentPage}: {pagePhotoIds.Count} items"); + + // Spawn grid items for this page + foreach (string photoId in pagePhotoIds) + { + SpawnGridItem(photoId); + } + + _currentPage++; + + // Update UI state + bool hasMore = _currentPage * itemsPerPage < _allPhotoIds.Count; + if (loadMoreButton != null) + loadMoreButton.gameObject.SetActive(hasMore); + + UpdateStatusText($"Showing {_activeGridItems.Count} of {_allPhotoIds.Count} photos"); + } + + /// + /// Spawn a grid item for a photo + /// + private void SpawnGridItem(string photoId) + { + if (_activeGridItems.ContainsKey(photoId)) + { + Logging.Warning($"[StatuePhotoGalleryController] Grid item already exists: {photoId}"); + return; + } + + PhotoGridItem gridItem = Instantiate(gridItemPrefab, gridContainer); + gridItem.Initialize(photoId, this); + + _activeGridItems[photoId] = gridItem; + + // Load thumbnail asynchronously + StartCoroutine(LoadThumbnailAsync(photoId, gridItem)); + } + + /// + /// Load thumbnail for grid item (async to avoid frame hitches) + /// + private IEnumerator LoadThumbnailAsync(string photoId, PhotoGridItem gridItem) + { + // Check cache first + if (_thumbnailCache.TryGetValue(photoId, out Texture2D cachedThumbnail)) + { + gridItem.SetThumbnail(cachedThumbnail); + yield break; + } + + // Yield to avoid loading all thumbnails in one frame + yield return null; + + // Load full photo + Texture2D fullPhoto = StatuePhotoManager.LoadPhoto(photoId); + + if (fullPhoto == null) + { + Logging.Warning($"[StatuePhotoGalleryController] Failed to load photo: {photoId}"); + yield break; + } + + // Create thumbnail + Texture2D thumbnail = StatuePhotoManager.CreateThumbnail(fullPhoto, thumbnailSize); + + // Destroy full photo immediately (we only need thumbnail) + Destroy(fullPhoto); + + // Cache thumbnail + CacheThumbnail(photoId, thumbnail); + + // Set on grid item + if (gridItem != null) + { + gridItem.SetThumbnail(thumbnail); + } + } + + /// + /// Cache thumbnail with LRU eviction + /// + private void CacheThumbnail(string photoId, Texture2D thumbnail) + { + // Add to cache + _thumbnailCache[photoId] = thumbnail; + _thumbnailCacheOrder.Enqueue(photoId); + + // Evict oldest if over limit + while (_thumbnailCache.Count > maxCachedThumbnails && _thumbnailCacheOrder.Count > 0) + { + string oldestId = _thumbnailCacheOrder.Dequeue(); + + if (_thumbnailCache.TryGetValue(oldestId, out Texture2D oldThumbnail)) + { + Destroy(oldThumbnail); + _thumbnailCache.Remove(oldestId); + Logging.Debug($"[StatuePhotoGalleryController] Evicted thumbnail from cache: {oldestId}"); + } + } + } + + /// + /// Show enlarged view of a photo (called by PhotoGridItem) + /// + public void ShowEnlargedView(string photoId) + { + if (enlargedViewPanel == null || enlargedPhotoImage == null) + { + Logging.Warning("[StatuePhotoGalleryController] Enlarged view UI not configured"); + return; + } + + Logging.Debug($"[StatuePhotoGalleryController] Showing enlarged view: {photoId}"); + + // Clear previous enlarged texture + if (_currentEnlargedTexture != null) + { + Destroy(_currentEnlargedTexture); + _currentEnlargedTexture = null; + } + + // Load full-size photo + _currentEnlargedTexture = StatuePhotoManager.LoadPhoto(photoId); + + if (_currentEnlargedTexture == null) + { + Logging.Error($"[StatuePhotoGalleryController] Failed to load enlarged photo: {photoId}"); + return; + } + + // Create sprite from texture + Sprite enlargedSprite = Sprite.Create( + _currentEnlargedTexture, + new Rect(0, 0, _currentEnlargedTexture.width, _currentEnlargedTexture.height), + new Vector2(0.5f, 0.5f) + ); + + enlargedPhotoImage.sprite = enlargedSprite; + _currentEnlargedPhotoId = photoId; + + // Update photo info + UpdatePhotoInfo(photoId); + + // Show panel + enlargedViewPanel.SetActive(true); + } + + /// + /// Close enlarged view + /// + private void CloseEnlargedView() + { + if (enlargedViewPanel != null) + enlargedViewPanel.SetActive(false); + + // Clean up texture + if (_currentEnlargedTexture != null) + { + Destroy(_currentEnlargedTexture); + _currentEnlargedTexture = null; + } + + _currentEnlargedPhotoId = null; + } + + /// + /// Delete currently viewed photo + /// + private void DeleteCurrentPhoto() + { + if (string.IsNullOrEmpty(_currentEnlargedPhotoId)) + { + Logging.Warning("[StatuePhotoGalleryController] No photo selected for deletion"); + return; + } + + string photoIdToDelete = _currentEnlargedPhotoId; + + // Close enlarged view first + CloseEnlargedView(); + + // Delete photo + bool deleted = StatuePhotoManager.DeletePhoto(photoIdToDelete); + + if (deleted) + { + // Remove from grid + if (_activeGridItems.TryGetValue(photoIdToDelete, out PhotoGridItem gridItem)) + { + Destroy(gridItem.gameObject); + _activeGridItems.Remove(photoIdToDelete); + } + + // Remove from cache + if (_thumbnailCache.TryGetValue(photoIdToDelete, out Texture2D thumbnail)) + { + Destroy(thumbnail); + _thumbnailCache.Remove(photoIdToDelete); + } + + // Refresh photo list + _allPhotoIds.Remove(photoIdToDelete); + + UpdateStatusText($"Photo deleted. {_allPhotoIds.Count} photos remaining"); + + Logging.Debug($"[StatuePhotoGalleryController] Photo deleted: {photoIdToDelete}"); + } + } + + /// + /// Update photo info text in enlarged view + /// + private void UpdatePhotoInfo(string photoId) + { + if (photoInfoText == null) return; + + StatuePhotoManager.PhotoMetadata metadata = StatuePhotoManager.GetPhotoMetadata(photoId); + + if (metadata != null) + { + System.DateTime timestamp = System.DateTime.Parse(metadata.timestamp); + string dateStr = timestamp.ToString("MMM dd, yyyy hh:mm tt"); + + float fileSizeMB = metadata.fileSizeBytes / (1024f * 1024f); + + photoInfoText.text = $"Date: {dateStr}\n" + + $"Decorations: {metadata.decorationCount}\n" + + $"Size: {fileSizeMB:F2} MB"; + } + else + { + photoInfoText.text = "Photo information unavailable"; + } + } + + /// + /// Update status text + /// + private void UpdateStatusText(string message) + { + if (statusText != null) + statusText.text = message; + + Logging.Debug($"[StatuePhotoGalleryController] Status: {message}"); + } + + /// + /// Clear all grid items and cached data + /// + private void ClearGallery() + { + // Destroy grid items + foreach (var gridItem in _activeGridItems.Values) + { + if (gridItem != null) + Destroy(gridItem.gameObject); + } + _activeGridItems.Clear(); + + // Clear thumbnail cache + foreach (var thumbnail in _thumbnailCache.Values) + { + if (thumbnail != null) + Destroy(thumbnail); + } + _thumbnailCache.Clear(); + _thumbnailCacheOrder.Clear(); + + Logging.Debug("[StatuePhotoGalleryController] Gallery cleared"); + } + + internal override void OnManagedDestroy() + { + base.OnManagedDestroy(); + + // Cleanup + ClearGallery(); + CloseEnlargedView(); + + // Unsubscribe buttons + if (closeEnlargedButton != null) + closeEnlargedButton.onClick.RemoveListener(CloseEnlargedView); + + if (deletePhotoButton != null) + deletePhotoButton.onClick.RemoveListener(DeleteCurrentPhoto); + + if (loadMoreButton != null) + loadMoreButton.onClick.RemoveListener(LoadNextPage); + } + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs.meta new file mode 100644 index 00000000..a56e8706 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Controllers/StatuePhotoGalleryController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a7339274a0c54f8c9134942f84d47140 +timeCreated: 1764065004 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs b/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs new file mode 100644 index 00000000..74de576d --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Core; +using UnityEngine; +using SDev; + +namespace Minigames.StatueDressup.Utils +{ + /// + /// Manages statue photo capture, storage, and retrieval. + /// Supports area-limited screenshots, persistent file storage (mobile + PC), + /// and optimized gallery loading with pagination. + /// + public static class StatuePhotoManager + { + private const string PHOTO_FOLDER = "StatuePhotos"; + private const string PHOTO_PREFIX = "MrCementStatue_"; + private const string METADATA_KEY_PREFIX = "StatuePhoto_Meta_"; + private const string PHOTO_INDEX_KEY = "StatuePhoto_Index"; + + /// + /// Photo metadata stored in PlayerPrefs + /// + [System.Serializable] + public class PhotoMetadata + { + public string photoId; + public string timestamp; + public int decorationCount; + public long fileSizeBytes; + } + + #region Capture + + /// + /// Capture a specific area of the screen using Screenshot Helper + /// + /// RectTransform defining the capture region + /// Callback with captured Texture2D + /// Camera used for coordinate conversion (null = Camera.main) + public static void CaptureAreaPhoto(RectTransform captureArea, Action onComplete, Camera mainCamera = null) + { + if (captureArea == null) + { + Logging.Error("[StatuePhotoManager] CaptureArea RectTransform is null!"); + onComplete?.Invoke(null); + return; + } + + if (mainCamera == null) mainCamera = Camera.main; + + // Get screen rect from RectTransform + Rect screenRect = GetScreenRectFromRectTransform(captureArea, mainCamera); + + Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}"); + + // Use Screenshot Helper's Capture method + ScreenshotHelper.Instance.Capture( + screenRect.position, + screenRect.size, + (Texture2D texture) => + { + if (texture != null) + { + Logging.Debug($"[StatuePhotoManager] Photo captured: {texture.width}x{texture.height}"); + onComplete?.Invoke(texture); + } + else + { + Logging.Error("[StatuePhotoManager] Screenshot Helper returned null texture!"); + onComplete?.Invoke(null); + } + } + ); + } + + /// + /// Convert RectTransform world corners to screen space rect + /// + private static Rect GetScreenRectFromRectTransform(RectTransform rectTransform, Camera camera) + { + Vector3[] corners = new Vector3[4]; + rectTransform.GetWorldCorners(corners); + + Vector2 min = RectTransformUtility.WorldToScreenPoint(camera, corners[0]); + Vector2 max = RectTransformUtility.WorldToScreenPoint(camera, corners[2]); + + // Ensure positive dimensions + float width = Mathf.Abs(max.x - min.x); + float height = Mathf.Abs(max.y - min.y); + + return new Rect(min.x, min.y, width, height); + } + + #endregion + + #region Save/Load + + /// + /// Save photo to persistent storage with metadata + /// + /// Texture2D to save + /// Number of decorations placed (for metadata) + /// Photo ID if successful, null if failed + public static string SavePhoto(Texture2D photo, int decorationCount = 0) + { + if (photo == null) + { + Logging.Error("[StatuePhotoManager] Cannot save null photo"); + return null; + } + + try + { + // Generate unique photo ID + string photoId = $"{PHOTO_PREFIX}{DateTime.Now.Ticks}"; + + // Save texture using FileSaveUtil + string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG( + photo, + FileSaveUtil.AppPath.PersistentDataPath, + PHOTO_FOLDER, + photoId + ); + + // Calculate file size + FileInfo fileInfo = new FileInfo(savedPath); + long fileSize = fileInfo.Exists ? fileInfo.Length : 0; + + // Save metadata + PhotoMetadata metadata = new PhotoMetadata + { + photoId = photoId, + timestamp = DateTime.Now.ToString("o"), + decorationCount = decorationCount, + fileSizeBytes = fileSize + }; + + SaveMetadata(metadata); + AddToPhotoIndex(photoId); + + Logging.Debug($"[StatuePhotoManager] Photo saved: {savedPath} ({fileSize} bytes)"); + return photoId; + } + catch (Exception e) + { + Logging.Error($"[StatuePhotoManager] Failed to save photo: {e.Message}"); + return null; + } + } + + /// + /// Load photo texture from storage + /// + public static Texture2D LoadPhoto(string photoId) + { + if (string.IsNullOrEmpty(photoId)) + { + Logging.Warning("[StatuePhotoManager] PhotoId is null or empty"); + return null; + } + + try + { + string filePath = GetPhotoFilePath(photoId); + + if (!File.Exists(filePath)) + { + Logging.Warning($"[StatuePhotoManager] Photo not found: {filePath}"); + return null; + } + + byte[] fileData = File.ReadAllBytes(filePath); + Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false); + + if (texture.LoadImage(fileData)) + { + Logging.Debug($"[StatuePhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})"); + return texture; + } + else + { + Logging.Error($"[StatuePhotoManager] Failed to decode image: {photoId}"); + UnityEngine.Object.Destroy(texture); + return null; + } + } + catch (Exception e) + { + Logging.Error($"[StatuePhotoManager] Failed to load photo {photoId}: {e.Message}"); + return null; + } + } + + /// + /// Delete photo and its metadata + /// + public static bool DeletePhoto(string photoId) + { + if (string.IsNullOrEmpty(photoId)) return false; + + try + { + string filePath = GetPhotoFilePath(photoId); + + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + + DeleteMetadata(photoId); + RemoveFromPhotoIndex(photoId); + + Logging.Debug($"[StatuePhotoManager] Photo deleted: {photoId}"); + return true; + } + catch (Exception e) + { + Logging.Error($"[StatuePhotoManager] Failed to delete photo {photoId}: {e.Message}"); + return false; + } + } + + #endregion + + #region Gallery Support + + /// + /// Get all photo IDs sorted by timestamp (newest first) + /// + public static List GetAllPhotoIds() + { + string indexJson = PlayerPrefs.GetString(PHOTO_INDEX_KEY, "[]"); + List photoIds = JsonUtility.FromJson(WrapJsonArray(indexJson))?.ids ?? new List(); + + // Sort by timestamp descending (newest first) + photoIds.Sort((a, b) => + { + PhotoMetadata metaA = LoadMetadata(a); + PhotoMetadata metaB = LoadMetadata(b); + + DateTime dateA = DateTime.Parse(metaA?.timestamp ?? DateTime.MinValue.ToString("o")); + DateTime dateB = DateTime.Parse(metaB?.timestamp ?? DateTime.MinValue.ToString("o")); + + return dateB.CompareTo(dateA); + }); + + return photoIds; + } + + /// + /// Get paginated photo IDs for optimized gallery loading + /// + /// Page number (0-indexed) + /// Number of items per page + public static List GetPhotoIdsPage(int page, int pageSize) + { + List allIds = GetAllPhotoIds(); + int startIndex = page * pageSize; + + if (startIndex >= allIds.Count) return new List(); + + int count = Mathf.Min(pageSize, allIds.Count - startIndex); + return allIds.GetRange(startIndex, count); + } + + /// + /// Get total number of saved photos + /// + public static int GetPhotoCount() + { + return GetAllPhotoIds().Count; + } + + /// + /// Get latest photo ID (most recent) + /// + public static string GetLatestPhotoId() + { + List allIds = GetAllPhotoIds(); + return allIds.Count > 0 ? allIds[0] : null; + } + + /// + /// Load photo metadata + /// + public static PhotoMetadata GetPhotoMetadata(string photoId) + { + return LoadMetadata(photoId); + } + + /// + /// Create thumbnail from full-size photo (for gallery preview) + /// + public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256) + { + if (fullSizePhoto == null) return null; + + int width = fullSizePhoto.width; + int height = fullSizePhoto.height; + + // Calculate thumbnail size maintaining aspect ratio + float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height); + int thumbWidth = Mathf.RoundToInt(width * scale); + int thumbHeight = Mathf.RoundToInt(height * scale); + + // Create thumbnail using bilinear filtering + RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32); + RenderTexture.active = rt; + + Graphics.Blit(fullSizePhoto, rt); + + Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false); + thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0); + thumbnail.Apply(); + + RenderTexture.active = null; + RenderTexture.ReleaseTemporary(rt); + + return thumbnail; + } + + #endregion + + #region Internal Helpers + + public static string GetPhotoDirectory() + { + return Path.Combine(Application.persistentDataPath, PHOTO_FOLDER); + } + + private static string GetPhotoFilePath(string photoId) + { + return Path.Combine(GetPhotoDirectory(), $"{photoId}.png"); + } + + private static void SaveMetadata(PhotoMetadata metadata) + { + string json = JsonUtility.ToJson(metadata); + PlayerPrefs.SetString(METADATA_KEY_PREFIX + metadata.photoId, json); + PlayerPrefs.Save(); + } + + private static PhotoMetadata LoadMetadata(string photoId) + { + string json = PlayerPrefs.GetString(METADATA_KEY_PREFIX + photoId, null); + return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson(json); + } + + private static void DeleteMetadata(string photoId) + { + PlayerPrefs.DeleteKey(METADATA_KEY_PREFIX + photoId); + PlayerPrefs.Save(); + } + + private static void AddToPhotoIndex(string photoId) + { + List photoIds = GetAllPhotoIds(); + if (!photoIds.Contains(photoId)) + { + photoIds.Add(photoId); + SavePhotoIndex(photoIds); + } + } + + private static void RemoveFromPhotoIndex(string photoId) + { + List photoIds = GetAllPhotoIds(); + if (photoIds.Remove(photoId)) + { + SavePhotoIndex(photoIds); + } + } + + private static void SavePhotoIndex(List photoIds) + { + string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds }); + PlayerPrefs.SetString(PHOTO_INDEX_KEY, json); + PlayerPrefs.Save(); + } + + private static string WrapJsonArray(string json) + { + if (json.StartsWith("[")) return "{\"ids\":" + json + "}"; + return json; + } + + [System.Serializable] + private class PhotoIdList + { + public List ids = new List(); + } + + #endregion + } +} + diff --git a/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs.meta b/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs.meta new file mode 100644 index 00000000..e1e7f0e0 --- /dev/null +++ b/Assets/Scripts/Minigames/StatueDressup/Utils/StatuePhotoManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7f3e9a2b4c5d6e7f8a9b0c1d2e3f4a5b \ No newline at end of file