using System; using System.Collections; using System.Collections.Generic; using System.IO; using Core; using UnityEngine; namespace Utils { /// /// Generalized photo capture, storage, and retrieval manager. /// Supports multiple capture types (minigames, screenshots, etc.) with type-based configuration. /// public static class PhotoManager { private const string RootCapturesFolder = "Captures"; /// /// Photo metadata stored in PlayerPrefs /// [Serializable] public class PhotoMetadata { public string photoId; public CaptureType captureType; public string timestamp; public int genericMetadata; // Can be decoration count, score, collectibles, etc. public long fileSizeBytes; } #region Plug-and-Play Coroutine /// /// Capture and save photo in one coroutine call. Handles UI hiding, capture, save, and restoration. /// /// Type of capture (determines folder/prefix) /// RectTransform defining the capture region /// Optional UI elements to hide during capture /// Callback with photoId on success /// Callback with error message on failure /// Generic metadata (decoration count, score, etc.) /// Camera for coordinate conversion (null = Camera.main) /// If true, clamps capture area to visible screen public static IEnumerator CaptureAndSaveCoroutine( CaptureType captureType, RectTransform captureArea, GameObject[] uiToHide = null, Action onSuccess = null, Action onFailure = null, int metadata = 0, Camera mainCamera = null, bool clampToScreenBounds = true) { if (captureArea == null) { string error = "[PhotoManager] CaptureArea RectTransform is null!"; Logging.Error(error); onFailure?.Invoke(error); yield break; } // Hide UI elements if (uiToHide != null) { foreach (var obj in uiToHide) { if (obj != null) obj.SetActive(false); } } // Wait for UI to hide yield return new WaitForEndOfFrame(); // Capture photo bool captureComplete = false; Texture2D capturedPhoto = null; CaptureAreaPhoto(captureArea, (texture) => { capturedPhoto = texture; captureComplete = true; }, mainCamera, clampToScreenBounds); // Wait for capture to complete yield return new WaitUntil(() => captureComplete); // Restore UI elements if (uiToHide != null) { foreach (var obj in uiToHide) { if (obj != null) obj.SetActive(true); } } // Save photo if (capturedPhoto != null) { string photoId = SavePhoto(captureType, capturedPhoto, metadata); if (!string.IsNullOrEmpty(photoId)) { Logging.Debug($"[PhotoManager] Photo saved successfully: {photoId}"); onSuccess?.Invoke(photoId); } else { string error = "[PhotoManager] Failed to save photo!"; Logging.Error(error); onFailure?.Invoke(error); } } else { string error = "[PhotoManager] Photo capture returned null!"; Logging.Error(error); onFailure?.Invoke(error); } } #endregion #region Capture /// /// Capture a specific area of the screen using Screenshot Helper /// public static void CaptureAreaPhoto( RectTransform captureArea, Action onComplete, Camera mainCamera = null, bool clampToScreenBounds = true) { if (captureArea == null) { Logging.Error("[PhotoManager] CaptureArea RectTransform is null!"); onComplete?.Invoke(null); return; } if (mainCamera == null) mainCamera = Camera.main; // Use ScreenSpaceUtility to convert RectTransform to screen rect Rect screenRect = ScreenSpaceUtility.RectTransformToScreenRect( captureArea, mainCamera, clampToScreenBounds, returnCenterPosition: true ); Logging.Debug($"[PhotoManager] 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($"[PhotoManager] Photo captured: {texture.width}x{texture.height}"); onComplete?.Invoke(texture); } else { Logging.Error("[PhotoManager] Screenshot Helper returned null texture!"); onComplete?.Invoke(null); } } ); } #endregion #region Save/Load /// /// Save photo to persistent storage with metadata /// public static string SavePhoto(CaptureType captureType, Texture2D photo, int metadata = 0) { if (photo == null) { Logging.Error("[PhotoManager] Cannot save null photo"); return null; } try { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); // Generate unique photo ID string photoId = $"{config.photoPrefix}{DateTime.Now.Ticks}"; // Get capture directory for this type string captureDirectory = GetCaptureDirectory(captureType); // Ensure directory exists if (!Directory.Exists(captureDirectory)) { Directory.CreateDirectory(captureDirectory); } // Save texture string filePath = Path.Combine(captureDirectory, $"{photoId}.png"); byte[] pngData = photo.EncodeToPNG(); File.WriteAllBytes(filePath, pngData); // Calculate file size FileInfo fileInfo = new FileInfo(filePath); long fileSize = fileInfo.Exists ? fileInfo.Length : 0; // Save metadata PhotoMetadata photoMetadata = new PhotoMetadata { photoId = photoId, captureType = captureType, timestamp = DateTime.Now.ToString("o"), genericMetadata = metadata, fileSizeBytes = fileSize }; SaveMetadata(captureType, photoMetadata); AddToPhotoIndex(captureType, photoId); Logging.Debug($"[PhotoManager] Photo saved: {filePath} ({fileSize} bytes)"); return photoId; } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to save photo: {e.Message}"); return null; } } /// /// Load photo texture from storage /// public static Texture2D LoadPhoto(CaptureType captureType, string photoId) { if (string.IsNullOrEmpty(photoId)) { Logging.Warning("[PhotoManager] PhotoId is null or empty"); return null; } try { string filePath = GetPhotoFilePath(captureType, photoId); if (!File.Exists(filePath)) { Logging.Warning($"[PhotoManager] 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($"[PhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})"); return texture; } else { Logging.Error($"[PhotoManager] Failed to decode image: {photoId}"); UnityEngine.Object.Destroy(texture); return null; } } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to load photo {photoId}: {e.Message}"); return null; } } /// /// Load multiple photos (most recent first) /// public static List LoadPhotos(CaptureType captureType, int count) { List photoIds = GetPhotoIds(captureType, count); List photos = new List(); foreach (string photoId in photoIds) { Texture2D photo = LoadPhoto(captureType, photoId); if (photo != null) { photos.Add(photo); } } return photos; } /// /// Load all photos for a capture type /// public static List LoadAllPhotos(CaptureType captureType) { return LoadPhotos(captureType, -1); } /// /// Delete photo and its metadata /// public static bool DeletePhoto(CaptureType captureType, string photoId) { if (string.IsNullOrEmpty(photoId)) return false; try { string filePath = GetPhotoFilePath(captureType, photoId); if (File.Exists(filePath)) { File.Delete(filePath); } DeleteMetadata(captureType, photoId); RemoveFromPhotoIndex(captureType, photoId); Logging.Debug($"[PhotoManager] Photo deleted: {photoId}"); return true; } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to delete photo {photoId}: {e.Message}"); return false; } } #endregion #region Retrieval & Queries /// /// Get photo IDs for a capture type (most recent first) /// /// Type of capture /// Number of IDs to return (-1 = all) public static List GetPhotoIds(CaptureType captureType, int count = -1) { List allIds = GetAllPhotoIds(captureType); if (count < 0 || count >= allIds.Count) { return allIds; } return allIds.GetRange(0, count); } /// /// Get all photo IDs sorted by timestamp (newest first) /// public static List GetAllPhotoIds(CaptureType captureType) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string indexJson = PlayerPrefs.GetString(config.indexKey, "[]"); List photoIds = JsonUtility.FromJson(WrapJsonArray(indexJson))?.ids ?? new List(); // Sort by timestamp descending (newest first) photoIds.Sort((a, b) => { PhotoMetadata metaA = LoadMetadata(captureType, a); PhotoMetadata metaB = LoadMetadata(captureType, 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 /// public static List GetPhotoIdsPage(CaptureType captureType, int page, int pageSize) { List allIds = GetAllPhotoIds(captureType); 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(CaptureType captureType) { return GetAllPhotoIds(captureType).Count; } /// /// Get latest photo ID (most recent) /// public static string GetLatestPhotoId(CaptureType captureType) { List allIds = GetAllPhotoIds(captureType); return allIds.Count > 0 ? allIds[0] : null; } /// /// Load photo metadata /// public static PhotoMetadata GetPhotoMetadata(CaptureType captureType, string photoId) { return LoadMetadata(captureType, photoId); } #endregion #region Utility Methods /// /// 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; } /// /// Get capture directory for a specific type /// public static string GetCaptureDirectory(CaptureType captureType) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); return Path.Combine(Application.persistentDataPath, RootCapturesFolder, config.subFolder); } #endregion #region Decoration Metadata /// /// Save decoration metadata for a photo /// Saves both with photo ID and as "latest" for easy reference /// public static void SaveDecorationMetadata(CaptureType captureType, string photoId, T metadata) where T : class { try { string directory = GetCaptureDirectory(captureType); // Ensure directory exists if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } string json = JsonUtility.ToJson(metadata, true); // Save with photo ID (for loading specific photo's decorations) string specificPath = Path.Combine(directory, $"{photoId}_decorations.json"); File.WriteAllText(specificPath, json); // Also save as "latest" for easy reference CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string latestPath = Path.Combine(directory, $"{config.subFolder}_latest.json"); File.WriteAllText(latestPath, json); Logging.Debug($"[PhotoManager] Decoration metadata saved: {specificPath}"); } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to save decoration metadata: {e.Message}"); } } /// /// Load decoration metadata for a specific photo /// public static T LoadDecorationMetadata(CaptureType captureType, string photoId) where T : class { try { string directory = GetCaptureDirectory(captureType); string filePath = Path.Combine(directory, $"{photoId}_decorations.json"); if (!File.Exists(filePath)) { Logging.Warning($"[PhotoManager] Decoration metadata not found: {filePath}"); return null; } string json = File.ReadAllText(filePath); T metadata = JsonUtility.FromJson(json); Logging.Debug($"[PhotoManager] Decoration metadata loaded: {filePath}"); return metadata; } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to load decoration metadata: {e.Message}"); return null; } } /// /// Load the latest decoration metadata (most recent capture) /// public static T LoadLatestDecorationMetadata(CaptureType captureType) where T : class { try { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string directory = GetCaptureDirectory(captureType); string filePath = Path.Combine(directory, $"{config.subFolder}_latest.json"); if (!File.Exists(filePath)) { Logging.Warning($"[PhotoManager] Latest decoration metadata not found: {filePath}"); return null; } string json = File.ReadAllText(filePath); T metadata = JsonUtility.FromJson(json); Logging.Debug($"[PhotoManager] Latest decoration metadata loaded: {filePath}"); return metadata; } catch (Exception e) { Logging.Error($"[PhotoManager] Failed to load latest decoration metadata: {e.Message}"); return null; } } #endregion #region Internal Helpers private static string GetPhotoFilePath(CaptureType captureType, string photoId) { return Path.Combine(GetCaptureDirectory(captureType), $"{photoId}.png"); } private static void SaveMetadata(CaptureType captureType, PhotoMetadata metadata) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string json = JsonUtility.ToJson(metadata); PlayerPrefs.SetString(config.metadataPrefix + metadata.photoId, json); PlayerPrefs.Save(); } private static PhotoMetadata LoadMetadata(CaptureType captureType, string photoId) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string json = PlayerPrefs.GetString(config.metadataPrefix + photoId, null); return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson(json); } private static void DeleteMetadata(CaptureType captureType, string photoId) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); PlayerPrefs.DeleteKey(config.metadataPrefix + photoId); PlayerPrefs.Save(); } private static void AddToPhotoIndex(CaptureType captureType, string photoId) { List photoIds = GetAllPhotoIds(captureType); if (!photoIds.Contains(photoId)) { photoIds.Add(photoId); SavePhotoIndex(captureType, photoIds); } } private static void RemoveFromPhotoIndex(CaptureType captureType, string photoId) { List photoIds = GetAllPhotoIds(captureType); if (photoIds.Remove(photoId)) { SavePhotoIndex(captureType, photoIds); } } private static void SavePhotoIndex(CaptureType captureType, List photoIds) { CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType); string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds }); PlayerPrefs.SetString(config.indexKey, 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 } }