using System; using System.Collections.Generic; using System.IO; using Core; using UnityEngine; using SDev; using ScreenUtils = Utils.ScreenSpaceUtility; 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 PhotoFolder = "StatuePhotos"; private const string PhotoPrefix = "MrCementStatue_"; private const string MetadataKeyPrefix = "StatuePhoto_Meta_"; private const string PhotoIndexKey = "StatuePhoto_Index"; /// /// Photo metadata stored in PlayerPrefs /// [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) /// If true, clamps capture area to visible screen bounds public static void CaptureAreaPhoto( RectTransform captureArea, Action onComplete, Camera mainCamera = null, bool clampToScreenBounds = true) { if (captureArea == null) { Logging.Error("[StatuePhotoManager] CaptureArea RectTransform is null!"); onComplete?.Invoke(null); return; } if (mainCamera == null) mainCamera = Camera.main; // Use ScreenSpaceUtility to convert RectTransform to screen rect // returnCenterPosition = true because ScreenshotHelper expects center position Rect screenRect = ScreenUtils.RectTransformToScreenRect( captureArea, mainCamera, clampToScreenBounds, returnCenterPosition: true ); Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}"); // Use Screenshot Helper's Capture method ScreenshotHelper.Instance.Capture( screenRect.position, screenRect.size, (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); } } ); } #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 = $"{PhotoPrefix}{DateTime.Now.Ticks}"; // Save texture using FileSaveUtil string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG( photo, FileSaveUtil.AppPath.PersistentDataPath, PhotoFolder, 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(PhotoIndexKey, "[]"); 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, PhotoFolder); } 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(MetadataKeyPrefix + metadata.photoId, json); PlayerPrefs.Save(); } private static PhotoMetadata LoadMetadata(string photoId) { string json = PlayerPrefs.GetString(MetadataKeyPrefix + photoId, null); return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson(json); } private static void DeleteMetadata(string photoId) { PlayerPrefs.DeleteKey(MetadataKeyPrefix + 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(PhotoIndexKey, json); PlayerPrefs.Save(); } private static string WrapJsonArray(string json) { if (json.StartsWith("[")) return "{\"ids\":" + json + "}"; return json; } [Serializable] private class PhotoIdList { public List ids = new List(); } #endregion } }