Add seeing mr Cement photos in the album

This commit is contained in:
Michal Pikulski
2025-12-15 15:24:17 +01:00
parent de02394198
commit f72b8f3cf5
20 changed files with 2194 additions and 230 deletions

View File

@@ -0,0 +1,312 @@
using System.Collections.Generic;
using System.Linq;
using CardSystem.UI.Component;
using Core;
using Minigames.StatueDressup.Controllers;
using Minigames.StatueDressup.PhotoGallery;
using UnityEngine;
using Utils;
namespace CardSystem.Controllers
{
/// <summary>
/// Controller for managing photo slots across album pages.
/// Discovers all AlbumPhotoSlot prefabs in scene, groups by CaptureType,
/// and populates with latest photos from PhotoManager.
/// </summary>
public class AlbumPhotoPageController
{
private readonly GameObject _backdrop;
private readonly Transform _enlargedContainer;
private readonly float _animationDuration;
private Dictionary<CaptureType, List<AlbumPhotoSlot>> _slotsByType;
private Dictionary<AlbumPhotoSlot, string> _photoAssignments; // Maps each slot to its assigned photoId
private PhotoEnlargeController _enlargeController;
private bool _isInitialized;
public bool IsInitialized => _isInitialized;
/// <summary>
/// Constructor
/// </summary>
/// <param name="backdrop">Backdrop for photo enlargement</param>
/// <param name="enlargedContainer">Container for enlarged photos</param>
/// <param name="animationDuration">Duration for enlarge/shrink animations</param>
public AlbumPhotoPageController(GameObject backdrop, Transform enlargedContainer, float animationDuration = 0.3f)
{
_backdrop = backdrop;
_enlargedContainer = enlargedContainer;
_animationDuration = animationDuration;
// Initialize data structures
_photoAssignments = new Dictionary<AlbumPhotoSlot, string>();
// Initialize enlarge controller
_enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, animationDuration);
Logging.Debug("[AlbumPhotoPageController] Controller created");
}
/// <summary>
/// Discover all photo slots in scene and populate them with latest photos
/// </summary>
public void Initialize()
{
Logging.Debug("[AlbumPhotoPageController] Initialize() called");
if (_isInitialized)
{
Logging.Warning("[AlbumPhotoPageController] Already initialized");
return;
}
// Find all photo slots in scene
AlbumPhotoSlot[] allSlots = Object.FindObjectsByType<AlbumPhotoSlot>(FindObjectsInactive.Include, FindObjectsSortMode.None);
Logging.Debug($"[AlbumPhotoPageController] Found {(allSlots != null ? allSlots.Length : 0)} AlbumPhotoSlot components in scene");
if (allSlots == null || allSlots.Length == 0)
{
Logging.Warning("[AlbumPhotoPageController] No AlbumPhotoSlot components found in scene");
_isInitialized = true;
return;
}
// Group slots by CaptureType
_slotsByType = new Dictionary<CaptureType, List<AlbumPhotoSlot>>();
foreach (var slot in allSlots)
{
Logging.Debug($"[AlbumPhotoPageController] Processing slot: {slot.name}, CaptureType: {slot.CaptureType}");
if (!_slotsByType.ContainsKey(slot.CaptureType))
{
_slotsByType[slot.CaptureType] = new List<AlbumPhotoSlot>();
}
_slotsByType[slot.CaptureType].Add(slot);
// Initialize the slot with this controller
slot.Initialize(this);
}
Logging.Debug($"[AlbumPhotoPageController] Found {allSlots.Length} photo slots across {_slotsByType.Count} capture types");
// Log slot counts per type
foreach (var kvp in _slotsByType)
{
Logging.Debug($"[AlbumPhotoPageController] {kvp.Key}: {kvp.Value.Count} slots");
}
// Prepare photo assignments (but don't populate yet - slots will request when they become active)
Logging.Debug("[AlbumPhotoPageController] Starting PreparePhotoAssignments()");
PreparePhotoAssignments();
_isInitialized = true;
Logging.Debug("[AlbumPhotoPageController] Initialize() complete");
}
/// <summary>
/// Prepare photo assignments for all discovered slots.
/// Slots will request their assigned photo when they become active.
/// </summary>
private void PreparePhotoAssignments()
{
_photoAssignments.Clear();
foreach (var kvp in _slotsByType)
{
CaptureType captureType = kvp.Key;
List<AlbumPhotoSlot> slots = kvp.Value;
// Get latest photos for this capture type
int slotCount = slots.Count;
List<string> photoIds = PhotoManager.GetPhotoIds(captureType, slotCount);
Logging.Debug($"[AlbumPhotoPageController] Preparing assignments for {slotCount} slots of {captureType} with {photoIds.Count} photos");
// Build assignment map
for (int i = 0; i < slots.Count; i++)
{
string photoId = (i < photoIds.Count) ? photoIds[i] : null;
_photoAssignments[slots[i]] = photoId;
if (photoId != null)
{
Logging.Debug($"[AlbumPhotoPageController] Slot {i} ({slots[i].name}) -> {photoId}");
}
else
{
Logging.Debug($"[AlbumPhotoPageController] Slot {i} ({slots[i].name}) -> (no photo)");
}
}
}
Logging.Debug($"[AlbumPhotoPageController] Prepared {_photoAssignments.Count} photo assignments");
}
/// <summary>
/// Get the assigned photo ID for a specific slot.
/// Called by slots when they become active.
/// </summary>
public string GetAssignedPhotoId(AlbumPhotoSlot slot)
{
if (slot == null || _photoAssignments == null)
{
return null;
}
if (_photoAssignments.TryGetValue(slot, out string photoId))
{
return photoId;
}
Logging.Warning($"[AlbumPhotoPageController] Slot {slot.name} not found in assignments");
return null;
}
/// <summary>
/// Refresh photos for a specific capture type (call after new photo is taken)
/// </summary>
public void RefreshPhotosForType(CaptureType captureType)
{
if (!_isInitialized || !_slotsByType.ContainsKey(captureType))
{
return;
}
List<AlbumPhotoSlot> slots = _slotsByType[captureType];
// Clear existing slots
foreach (var slot in slots)
{
slot.Clear();
}
// Get latest photos and rebuild assignments
int slotCount = slots.Count;
List<string> photoIds = PhotoManager.GetPhotoIds(captureType, slotCount);
Logging.Debug($"[AlbumPhotoPageController] Refreshing assignments for {slotCount} slots of {captureType} with {photoIds.Count} photos");
// Update assignments
for (int i = 0; i < slots.Count; i++)
{
string photoId = (i < photoIds.Count) ? photoIds[i] : null;
_photoAssignments[slots[i]] = photoId;
}
// Trigger slots to reload (they will request their new assignment when they become active)
foreach (var slot in slots)
{
if (slot.gameObject.activeInHierarchy)
{
// If slot is currently active, immediately populate with new assignment
slot.PopulateWithPhoto(GetAssignedPhotoId(slot));
}
}
}
/// <summary>
/// Refresh all photo slots (call after album is opened to show latest photos)
/// </summary>
public void RefreshAllPhotos()
{
if (!_isInitialized)
{
return;
}
foreach (var captureType in _slotsByType.Keys.ToList())
{
RefreshPhotosForType(captureType);
}
Logging.Debug("[AlbumPhotoPageController] All photos refreshed");
}
/// <summary>
/// Enlarge a photo (called by AlbumPhotoSlot)
/// </summary>
public void EnlargePhoto(AlbumPhotoSlot slot, Texture2D fullSizeTexture)
{
if (_enlargeController == null)
{
Logging.Error("[AlbumPhotoPageController] Enlarge controller not initialized");
return;
}
// If already enlarged, shrink it
if (_enlargeController.IsPhotoEnlarged)
{
_enlargeController.ShrinkPhoto();
return;
}
if (fullSizeTexture == null)
{
Logging.Error($"[AlbumPhotoPageController] Cannot enlarge null texture for slot {slot.PhotoId}");
return;
}
Logging.Debug($"[AlbumPhotoPageController] Enlarging photo: {slot.PhotoId} from {slot.CaptureType}");
// Default enlarged scale (can be made configurable)
float enlargedScale = 2.5f;
// Enlarge using the controller
_enlargeController.EnlargePhoto(slot, slot.gameObject, fullSizeTexture, enlargedScale);
}
/// <summary>
/// Get photo count for a specific capture type
/// </summary>
public int GetPhotoCount(CaptureType captureType)
{
return PhotoManager.GetPhotoCount(captureType);
}
/// <summary>
/// Get slot count for a specific capture type
/// </summary>
public int GetSlotCount(CaptureType captureType)
{
if (!_isInitialized || !_slotsByType.ContainsKey(captureType))
{
return 0;
}
return _slotsByType[captureType].Count;
}
/// <summary>
/// Cleanup when album is closed
/// </summary>
public void Cleanup()
{
if (_enlargeController != null)
{
_enlargeController.Cleanup();
}
// Clear all slots
if (_slotsByType != null)
{
foreach (var slots in _slotsByType.Values)
{
foreach (var slot in slots)
{
if (slot != null)
{
slot.Clear();
}
}
}
}
Logging.Debug("[AlbumPhotoPageController] Cleanup complete");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 607fe563cc4a4d48976d845145bc0d57
timeCreated: 1765801157

View File

@@ -0,0 +1,257 @@
using System.Collections;
using CardSystem.Controllers;
using Core;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Utils;
namespace CardSystem.UI.Component
{
/// <summary>
/// Photo display slot for the album book pages.
/// Pre-placed prefab that displays photos from a specific minigame category.
/// Supports click-to-enlarge functionality.
/// </summary>
public class AlbumPhotoSlot : MonoBehaviour, IPointerClickHandler
{
[Header("Configuration")]
[Tooltip("Which minigame category this slot displays photos from")]
[SerializeField] private CaptureType captureType = CaptureType.StatueMinigame;
[Header("References")]
[SerializeField] private Image photoImage;
[SerializeField] private GameObject loadingIndicator;
[SerializeField] private GameObject emptyPlaceholder;
[Header("Visual Settings")]
[SerializeField] private int thumbnailSize = 256;
private string _photoId;
private Texture2D _currentTexture;
private Texture2D _fullSizeTexture;
private AlbumPhotoPageController _controller;
private bool _isPopulated;
public CaptureType CaptureType => captureType;
public bool IsPopulated => _isPopulated;
public string PhotoId => _photoId;
/// <summary>
/// Initialize the slot with controller reference
/// </summary>
public void Initialize(AlbumPhotoPageController controller)
{
_controller = controller;
Logging.Debug($"[AlbumPhotoSlot] {gameObject.name} initialized with controller");
}
/// <summary>
/// When slot becomes active, request and load assigned photo from controller
/// </summary>
private void OnEnable()
{
// Only auto-load if we have a controller and haven't already populated
if (_controller != null && !_isPopulated && string.IsNullOrEmpty(_photoId))
{
Logging.Debug($"[AlbumPhotoSlot] {gameObject.name} became active, requesting assigned photo");
// Request our assigned photo ID from controller
string assignedPhotoId = _controller.GetAssignedPhotoId(this);
if (!string.IsNullOrEmpty(assignedPhotoId))
{
Logging.Debug($"[AlbumPhotoSlot] {gameObject.name} assigned photoId: {assignedPhotoId}");
PopulateWithPhoto(assignedPhotoId);
}
else
{
Logging.Debug($"[AlbumPhotoSlot] {gameObject.name} has no assigned photo, showing empty state");
ShowEmptyState();
}
}
}
/// <summary>
/// Populate this slot with a photo from the minigame
/// </summary>
/// <param name="photoId">Photo ID to load</param>
public void PopulateWithPhoto(string photoId)
{
Logging.Debug($"[AlbumPhotoSlot] PopulateWithPhoto called: photoId='{photoId}', captureType={captureType}");
if (string.IsNullOrEmpty(photoId))
{
Logging.Debug($"[AlbumPhotoSlot] Photo ID is null or empty, showing empty state");
ShowEmptyState();
return;
}
_photoId = photoId;
StartCoroutine(LoadPhotoAsync(photoId));
}
/// <summary>
/// Load photo asynchronously to avoid frame hitches
/// </summary>
private IEnumerator LoadPhotoAsync(string photoId)
{
ShowLoadingState();
Logging.Debug($"[AlbumPhotoSlot] LoadPhotoAsync started for photoId: {photoId}, captureType: {captureType}");
// Yield to avoid loading all photos in one frame
yield return null;
// Load full photo
Logging.Debug($"[AlbumPhotoSlot] Calling PhotoManager.LoadPhoto({captureType}, {photoId})");
Texture2D fullPhoto = PhotoManager.LoadPhoto(captureType, photoId);
if (fullPhoto == null)
{
Logging.Warning($"[AlbumPhotoSlot] Failed to load photo: {photoId} for {captureType}");
ShowEmptyState();
yield break;
}
Logging.Debug($"[AlbumPhotoSlot] Photo loaded successfully! Size: {fullPhoto.width}x{fullPhoto.height}");
// Create thumbnail for display
Texture2D thumbnail = PhotoManager.CreateThumbnail(fullPhoto, thumbnailSize);
Logging.Debug($"[AlbumPhotoSlot] Thumbnail created: {thumbnail.width}x{thumbnail.height}");
// Store full size for enlargement
_fullSizeTexture = fullPhoto;
_currentTexture = thumbnail;
// Display thumbnail
DisplayPhoto(thumbnail);
_isPopulated = true;
Logging.Debug($"[AlbumPhotoSlot] Photo display complete for {photoId}");
}
/// <summary>
/// Display photo texture on the image component
/// </summary>
private void DisplayPhoto(Texture2D texture)
{
if (texture == null || photoImage == null) return;
// Create sprite from texture
Sprite photoSprite = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f)
);
photoImage.sprite = photoSprite;
photoImage.enabled = true;
HideLoadingState();
HideEmptyState();
}
/// <summary>
/// Handle click to enlarge photo
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
if (!_isPopulated || string.IsNullOrEmpty(_photoId) || _controller == null)
{
return;
}
Logging.Debug($"[AlbumPhotoSlot] Clicked photo: {_photoId} from {captureType}");
// Request enlargement from controller
_controller.EnlargePhoto(this, _fullSizeTexture);
}
/// <summary>
/// Clear the slot
/// </summary>
public void Clear()
{
_photoId = null;
_isPopulated = false;
if (_currentTexture != null)
{
Destroy(_currentTexture);
_currentTexture = null;
}
if (_fullSizeTexture != null)
{
Destroy(_fullSizeTexture);
_fullSizeTexture = null;
}
if (photoImage != null)
{
photoImage.sprite = null;
photoImage.enabled = false;
}
ShowEmptyState();
}
#region Visual States
private void ShowLoadingState()
{
if (loadingIndicator != null)
loadingIndicator.SetActive(true);
if (photoImage != null)
photoImage.enabled = false;
if (emptyPlaceholder != null)
emptyPlaceholder.SetActive(false);
}
private void HideLoadingState()
{
if (loadingIndicator != null)
loadingIndicator.SetActive(false);
}
private void ShowEmptyState()
{
if (emptyPlaceholder != null)
emptyPlaceholder.SetActive(true);
if (loadingIndicator != null)
loadingIndicator.SetActive(false);
if (photoImage != null)
photoImage.enabled = false;
_isPopulated = false;
}
private void HideEmptyState()
{
if (emptyPlaceholder != null)
emptyPlaceholder.SetActive(false);
}
#endregion
private void OnDestroy()
{
// Cleanup textures
if (_currentTexture != null)
{
Destroy(_currentTexture);
}
if (_fullSizeTexture != null)
{
Destroy(_fullSizeTexture);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb3200565b9f4a14b27f74ff9ec083c6
timeCreated: 1765801126

View File

@@ -20,6 +20,7 @@ namespace UI.CardSystem
[Header("Tab Configuration")]
[SerializeField] private int targetPage;
[Tooltip("Optional: Set this for card collection zone tabs. Leave unassigned for other tabs (e.g., photo pages).")]
[SerializeField] private CardZone zone;
[Header("Visual Settings")]

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using AppleHills.Data.CardSystem;
using CardSystem.Controllers;
using Core;
using Data.CardSystem;
using Pixelplacement;
@@ -8,6 +9,7 @@ using UI.DragAndDrop.Core;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Utils;
namespace UI.CardSystem
{
@@ -35,6 +37,11 @@ namespace UI.CardSystem
[SerializeField] private GameObject cardEnlargedBackdrop; // Backdrop to block interactions
[SerializeField] private Transform cardEnlargedContainer; // Container for enlarged cards (sits above backdrop)
[Header("Photo Gallery System")]
[SerializeField] private GameObject photoEnlargedBackdrop; // Backdrop for photo enlargement
[SerializeField] private Transform photoEnlargedContainer; // Container for enlarged photos
[SerializeField] private float photoAnimationDuration = 0.3f;
[Header("Booster Pack UI")]
[SerializeField] private GameObject[] boosterPackButtons;
[SerializeField] private BoosterOpeningPage boosterOpeningPage;
@@ -61,6 +68,13 @@ namespace UI.CardSystem
cardEnlargedContainer
);
private AlbumPhotoPageController _photoController;
private AlbumPhotoPageController PhotoController => _photoController ??= new AlbumPhotoPageController(
photoEnlargedBackdrop,
photoEnlargedContainer,
photoAnimationDuration
);
/// <summary>
/// Query method: Check if the book is currently flipping to a page.
/// Used by card states to know if they should wait before placing.
@@ -84,6 +98,12 @@ namespace UI.CardSystem
cardEnlargedBackdrop.SetActive(false);
}
// Hide photo backdrop initially
if (photoEnlargedBackdrop != null)
{
photoEnlargedBackdrop.SetActive(false);
}
// Set up exit button
if (exitButton != null)
{
@@ -283,6 +303,13 @@ namespace UI.CardSystem
Logging.Debug("[AlbumViewPage] Switched to UI-only input mode on first entry");
}
// Initialize photo controller if not already done
if (!PhotoController.IsInitialized)
{
PhotoController.Initialize();
Logging.Debug("[AlbumViewPage] Photo controller initialized");
}
// Only spawn pending cards if we're already on an album page (not the menu)
if (IsInAlbumProper())
{
@@ -305,6 +332,12 @@ namespace UI.CardSystem
// Clean up active pending cards to prevent duplicates on next opening
CleanupPendingCornerCards();
// Clean up photo controller
if (_photoController != null)
{
_photoController.Cleanup();
}
// Don't restore input mode here - only restore when actually exiting (in OnExitButtonClicked)
base.TransitionOut();
}
@@ -329,6 +362,12 @@ namespace UI.CardSystem
// Clean up any enlarged card state before closing
CleanupEnlargedCardState();
// Clean up photo controller
if (_photoController != null)
{
_photoController.Cleanup();
}
// Simple fade out animation
if (canvasGroup != null)
{
@@ -401,6 +440,34 @@ namespace UI.CardSystem
Enlarge.UnregisterCard(card);
}
#endregion
#region Photo Gallery System
/// <summary>
/// Refresh photos for a specific capture type after a new photo is taken
/// </summary>
public void RefreshPhotosForType(CaptureType captureType)
{
if (_photoController != null && _photoController.IsInitialized)
{
_photoController.RefreshPhotosForType(captureType);
Logging.Debug($"[AlbumViewPage] Refreshed photos for {captureType}");
}
}
/// <summary>
/// Get photo count for a specific capture type
/// </summary>
public int GetPhotoCount(CaptureType captureType)
{
if (_photoController != null && _photoController.IsInitialized)
{
return _photoController.GetPhotoCount(captureType);
}
return 0;
}
#endregion
/// <summary>

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34a38264a29d495ba48983aff9916b13
timeCreated: 1765803616

View File

@@ -0,0 +1,251 @@
using System.IO;
using UnityEngine;
using Utils;
#if UNITY_EDITOR
namespace Minigames.Examples
{
/// <summary>
/// Debug tool for diagnosing photo loading issues.
/// Add to any GameObject in scene, then right-click in Inspector to run tests.
/// </summary>
public class PhotoSystemDebugger : MonoBehaviour
{
[ContextMenu("1. Check Photo Files on Disk")]
private void CheckPhotoFiles()
{
Debug.Log("=== CHECKING PHOTO FILES ON DISK ===");
CheckPhotoFilesForType(CaptureType.StatueMinigame);
CheckPhotoFilesForType(CaptureType.DivingMinigame);
}
private void CheckPhotoFilesForType(CaptureType type)
{
string path = PhotoManager.GetCaptureDirectory(type);
Debug.Log($"\n[{type}] Photo directory: {path}");
Debug.Log($"[{type}] Directory exists: {System.IO.Directory.Exists(path)}");
if (System.IO.Directory.Exists(path))
{
var files = System.IO.Directory.GetFiles(path, "*.png");
Debug.Log($"[{type}] PNG files found: {files.Length}");
if (files.Length > 0)
{
foreach (var file in files)
{
var fileInfo = new System.IO.FileInfo(file);
Debug.Log($" ✓ {System.IO.Path.GetFileName(file)} ({fileInfo.Length / 1024}KB)");
}
}
else
{
Debug.LogWarning($"[{type}] No PNG files found in directory!");
}
}
else
{
Debug.LogWarning($"[{type}] Directory does not exist! No photos captured yet.");
}
}
[ContextMenu("2. Check Disk Photos")]
private void CheckDiskPhotos()
{
Debug.Log("=== CHECKING DISK PHOTOS ===");
CheckPhotosForType(CaptureType.StatueMinigame);
CheckPhotosForType(CaptureType.DivingMinigame);
}
private void CheckPhotosForType(CaptureType type)
{
string directory = PhotoManager.GetCaptureDirectory(type);
Debug.Log($"\n[{type}] Photo directory: {directory}");
Debug.Log($"[{type}] Directory exists: {Directory.Exists(directory)}");
int count = PhotoManager.GetPhotoCount(type);
Debug.Log($"[{type}] PhotoManager.GetPhotoCount(): {count}");
if (count > 0)
{
var photoIds = PhotoManager.GetPhotoIds(type, 10); // Show top 10
Debug.Log($"[{type}] Latest {photoIds.Count} photos:");
foreach (var id in photoIds)
{
string filePath = Path.Combine(directory, $"{id}.png");
if (File.Exists(filePath))
{
FileInfo fileInfo = new FileInfo(filePath);
Debug.Log($" ✓ {id} - {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss} ({fileInfo.Length / 1024}KB)");
}
else
{
Debug.LogWarning($" ✗ {id} - FILE MISSING!");
}
}
}
else
{
Debug.LogWarning($"[{type}] No photos found on disk!");
}
}
[ContextMenu("3. Check AlbumPhotoSlot Components")]
private void CheckAlbumPhotoSlots()
{
Debug.Log("=== CHECKING ALBUM PHOTO SLOTS ===");
var slots = FindObjectsByType<CardSystem.UI.Component.AlbumPhotoSlot>(FindObjectsSortMode.None);
Debug.Log($"\nAlbumPhotoSlot components found in scene: {slots.Length}");
if (slots.Length == 0)
{
Debug.LogError("❌ NO ALBUMPHOTOSLOT COMPONENTS FOUND!");
Debug.LogError("You need to add AlbumPhotoSlot components to GameObjects on your album pages.");
return;
}
var statueSlots = 0;
var divingSlots = 0;
foreach (var slot in slots)
{
bool isActive = slot.gameObject.activeInHierarchy;
string status = isActive ? "✓ ACTIVE" : "✗ INACTIVE";
Debug.Log($" {status} - {slot.gameObject.name}");
Debug.Log($" └─ CaptureType: {slot.CaptureType}");
Debug.Log($" └─ Scene Path: {GetGameObjectPath(slot.gameObject)}");
if (slot.CaptureType == CaptureType.StatueMinigame) statueSlots++;
else if (slot.CaptureType == CaptureType.DivingMinigame) divingSlots++;
if (!isActive)
{
Debug.LogWarning($" └─ WARNING: Slot is inactive! Make sure parent page is active when album opens.");
}
}
Debug.Log($"\nSummary:");
Debug.Log($" StatueMinigame slots: {statueSlots}");
Debug.Log($" DivingMinigame slots: {divingSlots}");
}
[ContextMenu("4. Check Album Page State")]
private void CheckAlbumPageState()
{
Debug.Log("=== CHECKING ALBUM PAGE STATE ===");
var albumPage = FindFirstObjectByType<UI.CardSystem.AlbumViewPage>();
if (albumPage == null)
{
Debug.LogError("❌ AlbumViewPage not found in scene!");
return;
}
Debug.Log($"\n✓ AlbumViewPage found: {albumPage.gameObject.name}");
Debug.Log($" └─ GameObject active: {albumPage.gameObject.activeInHierarchy}");
Debug.Log($" └─ Component enabled: {albumPage.enabled}");
// Check if album is currently open
if (UI.Core.UIPageController.Instance != null)
{
bool isCurrentPage = UI.Core.UIPageController.Instance.CurrentPage == albumPage;
Debug.Log($" └─ Is current page: {isCurrentPage}");
if (!isCurrentPage)
{
Debug.LogWarning("Album is not currently open. Photo controller won't initialize until album opens.");
}
}
}
[ContextMenu("5. Test Photo Loading Directly")]
private void TestPhotoLoadingDirectly()
{
Debug.Log("=== TESTING DIRECT PHOTO LOADING ===");
TestLoadForType(CaptureType.StatueMinigame);
TestLoadForType(CaptureType.DivingMinigame);
}
private void TestLoadForType(CaptureType type)
{
Debug.Log($"\n[{type}] Testing photo load...");
var photoIds = PhotoManager.GetPhotoIds(type, 1);
if (photoIds.Count == 0)
{
Debug.LogWarning($"[{type}] No photos to load!");
return;
}
string photoId = photoIds[0];
Debug.Log($"[{type}] Attempting to load: {photoId}");
Texture2D photo = PhotoManager.LoadPhoto(type, photoId);
if (photo != null)
{
Debug.Log($"[{type}] ✓ Photo loaded successfully!");
Debug.Log($" └─ Resolution: {photo.width}x{photo.height}");
Debug.Log($" └─ Format: {photo.format}");
// Clean up
Destroy(photo);
}
else
{
Debug.LogError($"[{type}] ✗ Failed to load photo!");
Debug.LogError($" └─ Photo file may be missing or corrupted");
}
}
[ContextMenu("6. Run Full Diagnostic")]
private void RunFullDiagnostic()
{
Debug.Log("╔════════════════════════════════════════════╗");
Debug.Log("║ PHOTO SYSTEM FULL DIAGNOSTIC ║");
Debug.Log("╚════════════════════════════════════════════╝");
CheckPhotoFiles();
Debug.Log("──────────────────────────────────────────────────");
CheckDiskPhotos();
Debug.Log("\n" + new string('─', 50) + "\n");
CheckAlbumPhotoSlots();
Debug.Log("\n" + new string('─', 50) + "\n");
CheckAlbumPageState();
Debug.Log("\n" + new string('─', 50) + "\n");
TestPhotoLoadingDirectly();
Debug.Log("\n╔════════════════════════════════════════════╗");
Debug.Log("║ DIAGNOSTIC COMPLETE ║");
Debug.Log("╚════════════════════════════════════════════╝");
}
// Helper method to get full GameObject path
private string GetGameObjectPath(GameObject obj)
{
string path = obj.name;
Transform parent = obj.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cafdab23229342d5aabc0d6218984c5a
timeCreated: 1765803616

View File

@@ -2,6 +2,7 @@ using System.Collections;
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using Minigames.StatueDressup.PhotoGallery;
using UnityEngine;
using UnityEngine.UI;
using Utils;

View File

@@ -3,7 +3,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using Utils;
namespace Minigames.StatueDressup.Controllers
namespace Minigames.StatueDressup.PhotoGallery
{
/// <summary>
/// Manages enlarged photo preview display.
@@ -12,32 +12,32 @@ namespace Minigames.StatueDressup.Controllers
/// </summary>
public class PhotoEnlargeController
{
private readonly GameObject backdrop;
private readonly Transform enlargedContainer;
private readonly float animationDuration;
private readonly GameObject _backdrop;
private readonly Transform _enlargedContainer;
private readonly float _animationDuration;
private GameObject currentEnlargedPreview;
private PhotoGridItem currentSourceItem;
private GameObject _currentEnlargedPreview;
private MonoBehaviour _currentSourceItem;
/// <summary>
/// Constructor
/// </summary>
public PhotoEnlargeController(GameObject backdrop, Transform enlargedContainer, float animationDuration = 0.3f)
{
this.backdrop = backdrop;
this.enlargedContainer = enlargedContainer;
this.animationDuration = animationDuration;
this._backdrop = backdrop;
this._enlargedContainer = enlargedContainer;
this._animationDuration = animationDuration;
}
/// <summary>
/// Check if a photo is currently enlarged
/// </summary>
public bool IsPhotoEnlarged => currentEnlargedPreview != null;
public bool IsPhotoEnlarged => _currentEnlargedPreview != null;
/// <summary>
/// Enlarge a photo from a grid item
/// </summary>
public void EnlargePhoto(PhotoGridItem sourceItem, GameObject previewPrefab, Texture2D photoTexture, float enlargedScale)
public void EnlargePhoto(MonoBehaviour sourceItem, GameObject previewPrefab, Texture2D photoTexture, float enlargedScale)
{
if (sourceItem == null || previewPrefab == null || photoTexture == null)
{
@@ -46,30 +46,30 @@ namespace Minigames.StatueDressup.Controllers
}
// Don't allow multiple enlargements
if (currentEnlargedPreview != null)
if (_currentEnlargedPreview != null)
{
Logging.Warning("[PhotoEnlargeController] Photo already enlarged");
return;
}
currentSourceItem = sourceItem;
_currentSourceItem = sourceItem;
// Show backdrop
if (backdrop != null)
if (_backdrop != null)
{
backdrop.SetActive(true);
_backdrop.SetActive(true);
}
// Spawn preview clone
currentEnlargedPreview = Object.Instantiate(previewPrefab, enlargedContainer);
currentEnlargedPreview.transform.SetAsLastSibling();
_currentEnlargedPreview = Object.Instantiate(previewPrefab, _enlargedContainer);
_currentEnlargedPreview.transform.SetAsLastSibling();
// Position at source item's world position
currentEnlargedPreview.transform.position = sourceItem.transform.position;
currentEnlargedPreview.transform.localScale = sourceItem.transform.localScale;
_currentEnlargedPreview.transform.position = sourceItem.transform.position;
_currentEnlargedPreview.transform.localScale = sourceItem.transform.localScale;
// Set photo texture on preview
var previewImage = currentEnlargedPreview.GetComponent<UnityEngine.UI.Image>();
var previewImage = _currentEnlargedPreview.GetComponent<UnityEngine.UI.Image>();
if (previewImage != null)
{
// Create sprite from texture
@@ -82,10 +82,10 @@ namespace Minigames.StatueDressup.Controllers
}
// Add click handler to preview
var clickHandler = currentEnlargedPreview.GetComponent<EventTrigger>();
var clickHandler = _currentEnlargedPreview.GetComponent<EventTrigger>();
if (clickHandler == null)
{
clickHandler = currentEnlargedPreview.AddComponent<EventTrigger>();
clickHandler = _currentEnlargedPreview.AddComponent<EventTrigger>();
}
var pointerClickEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerClick };
@@ -93,8 +93,8 @@ namespace Minigames.StatueDressup.Controllers
clickHandler.triggers.Add(pointerClickEntry);
// Animate to center and scale up
TweenAnimationUtility.AnimateLocalPosition(currentEnlargedPreview.transform, Vector3.zero, animationDuration);
TweenAnimationUtility.AnimateScale(currentEnlargedPreview.transform, Vector3.one * enlargedScale, animationDuration);
TweenAnimationUtility.AnimateLocalPosition(_currentEnlargedPreview.transform, Vector3.zero, _animationDuration);
TweenAnimationUtility.AnimateScale(_currentEnlargedPreview.transform, Vector3.one * enlargedScale, _animationDuration);
// Play audio feedback
AudioManager.Instance.LoadAndPlayUIAudio("card_albumdrop_deep", false);
@@ -107,35 +107,35 @@ namespace Minigames.StatueDressup.Controllers
/// </summary>
public void ShrinkPhoto()
{
if (currentEnlargedPreview == null || currentSourceItem == null)
if (_currentEnlargedPreview == null || _currentSourceItem == null)
{
Logging.Warning("[PhotoEnlargeController] No photo to shrink");
return;
}
// Hide backdrop
if (backdrop != null)
if (_backdrop != null)
{
backdrop.SetActive(false);
_backdrop.SetActive(false);
}
// Get target position from source item
Vector3 targetWorldPos = currentSourceItem.transform.position;
Vector3 targetScale = currentSourceItem.transform.localScale;
Vector3 targetWorldPos = _currentSourceItem.transform.position;
Vector3 targetScale = _currentSourceItem.transform.localScale;
GameObject previewToDestroy = currentEnlargedPreview;
GameObject previewToDestroy = _currentEnlargedPreview;
// Animate back to source position
Pixelplacement.Tween.Position(previewToDestroy.transform, targetWorldPos, animationDuration, 0f, Pixelplacement.Tween.EaseInOut);
TweenAnimationUtility.AnimateScale(previewToDestroy.transform, targetScale, animationDuration, () =>
Pixelplacement.Tween.Position(previewToDestroy.transform, targetWorldPos, _animationDuration, 0f, Pixelplacement.Tween.EaseInOut);
TweenAnimationUtility.AnimateScale(previewToDestroy.transform, targetScale, _animationDuration, () =>
{
// Destroy preview after animation
Object.Destroy(previewToDestroy);
});
// Clear references
currentEnlargedPreview = null;
currentSourceItem = null;
_currentEnlargedPreview = null;
_currentSourceItem = null;
Logging.Debug("[PhotoEnlargeController] Photo shrunk");
}
@@ -145,18 +145,18 @@ namespace Minigames.StatueDressup.Controllers
/// </summary>
public void Cleanup()
{
if (currentEnlargedPreview != null)
if (_currentEnlargedPreview != null)
{
Object.Destroy(currentEnlargedPreview);
currentEnlargedPreview = null;
Object.Destroy(_currentEnlargedPreview);
_currentEnlargedPreview = null;
}
if (backdrop != null)
if (_backdrop != null)
{
backdrop.SetActive(false);
_backdrop.SetActive(false);
}
currentSourceItem = null;
_currentSourceItem = null;
}
}
}

View File

@@ -20,15 +20,11 @@ namespace Utils
{
public string subFolder;
public string photoPrefix;
public string metadataPrefix;
public string indexKey;
public CaptureConfig(string subFolder, string photoPrefix, string metadataPrefix, string indexKey)
public CaptureConfig(string subFolder, string photoPrefix)
{
this.subFolder = subFolder;
this.photoPrefix = photoPrefix;
this.metadataPrefix = metadataPrefix;
this.indexKey = indexKey;
}
}
@@ -41,16 +37,12 @@ namespace Utils
{
[CaptureType.StatueMinigame] = new CaptureConfig(
subFolder: "StatueMinigame",
photoPrefix: "Statue_",
metadataPrefix: "StatuePhoto_Meta_",
indexKey: "StatuePhoto_Index"
photoPrefix: "Statue_"
),
[CaptureType.DivingMinigame] = new CaptureConfig(
subFolder: "DivingMinigame",
photoPrefix: "Diving_",
metadataPrefix: "DivingPhoto_Meta_",
indexKey: "DivingPhoto_Index"
photoPrefix: "Diving_"
)
};

View File

@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Core;
using UnityEngine;
@@ -10,24 +11,12 @@ namespace Utils
/// <summary>
/// Generalized photo capture, storage, and retrieval manager.
/// Supports multiple capture types (minigames, screenshots, etc.) with type-based configuration.
/// Uses disk-only storage - no PlayerPrefs indexing (simplified for reliability).
/// </summary>
public static class PhotoManager
{
private const string RootCapturesFolder = "Captures";
/// <summary>
/// Photo metadata stored in PlayerPrefs
/// </summary>
[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
/// <summary>
@@ -189,7 +178,7 @@ namespace Utils
{
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
// Generate unique photo ID
// Generate unique photo filename using timestamp
string photoId = $"{config.photoPrefix}{DateTime.Now.Ticks}";
// Get capture directory for this type
@@ -201,29 +190,12 @@ namespace Utils
Directory.CreateDirectory(captureDirectory);
}
// Save texture
// Save PNG to disk (that's it - no PlayerPrefs!)
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)");
Logging.Debug($"[PhotoManager] Photo saved: {filePath}");
return photoId;
}
catch (Exception e)
@@ -305,7 +277,7 @@ namespace Utils
}
/// <summary>
/// Delete photo and its metadata
/// Delete photo and its associated files
/// </summary>
public static bool DeletePhoto(CaptureType captureType, string photoId)
{
@@ -313,15 +285,19 @@ namespace Utils
try
{
// Delete main photo
string filePath = GetPhotoFilePath(captureType, photoId);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
DeleteMetadata(captureType, photoId);
RemoveFromPhotoIndex(captureType, photoId);
// Delete decoration metadata if exists
string decorationPath = GetDecorationMetadataPath(captureType, photoId);
if (File.Exists(decorationPath))
{
File.Delete(decorationPath);
}
Logging.Debug($"[PhotoManager] Photo deleted: {photoId}");
return true;
@@ -338,44 +314,43 @@ namespace Utils
#region Retrieval & Queries
/// <summary>
/// Get photo IDs for a capture type (most recent first)
/// Get photo filenames sorted by newest first (scans disk)
/// </summary>
/// <param name="captureType">Type of capture</param>
/// <param name="count">Number of IDs to return (-1 = all)</param>
/// <param name="count">Number of filenames to return (-1 = all)</param>
public static List<string> GetPhotoIds(CaptureType captureType, int count = -1)
{
List<string> allIds = GetAllPhotoIds(captureType);
string directory = GetCaptureDirectory(captureType);
if (count < 0 || count >= allIds.Count)
if (!Directory.Exists(directory))
{
return allIds;
return new List<string>();
}
return allIds.GetRange(0, count);
try
{
// Get all PNG files, sorted by creation time (newest first)
var files = Directory.GetFiles(directory, "*.png")
.Select(f => new FileInfo(f))
.OrderByDescending(f => f.CreationTime)
.Select(f => Path.GetFileNameWithoutExtension(f.Name));
// Return top X or all
return (count > 0 ? files.Take(count) : files).ToList();
}
catch (Exception e)
{
Logging.Error($"[PhotoManager] Failed to scan directory {directory}: {e.Message}");
return new List<string>();
}
}
/// <summary>
/// Get all photo IDs sorted by timestamp (newest first)
/// Get all photo filenames sorted by newest first
/// </summary>
public static List<string> GetAllPhotoIds(CaptureType captureType)
{
CaptureConfig config = PhotoCaptureConfigs.GetConfig(captureType);
string indexJson = PlayerPrefs.GetString(config.indexKey, "[]");
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
// 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;
return GetPhotoIds(captureType, -1);
}
/// <summary>
@@ -401,22 +376,14 @@ namespace Utils
}
/// <summary>
/// Get latest photo ID (most recent)
/// Get latest photo filename (most recent)
/// </summary>
public static string GetLatestPhotoId(CaptureType captureType)
{
List<string> allIds = GetAllPhotoIds(captureType);
List<string> allIds = GetPhotoIds(captureType, 1);
return allIds.Count > 0 ? allIds[0] : null;
}
/// <summary>
/// Load photo metadata
/// </summary>
public static PhotoMetadata GetPhotoMetadata(CaptureType captureType, string photoId)
{
return LoadMetadata(captureType, photoId);
}
#endregion
#region Utility Methods
@@ -565,68 +532,18 @@ namespace Utils
private static string GetPhotoFilePath(CaptureType captureType, string photoId)
{
return Path.Combine(GetCaptureDirectory(captureType), $"{photoId}.png");
// Ensure .png extension
if (!photoId.EndsWith(".png"))
photoId += ".png";
return Path.Combine(GetCaptureDirectory(captureType), photoId);
}
private static void SaveMetadata(CaptureType captureType, PhotoMetadata metadata)
private static string GetDecorationMetadataPath(CaptureType captureType, string photoId)
{
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<PhotoMetadata>(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<string> photoIds = GetAllPhotoIds(captureType);
if (!photoIds.Contains(photoId))
{
photoIds.Add(photoId);
SavePhotoIndex(captureType, photoIds);
}
}
private static void RemoveFromPhotoIndex(CaptureType captureType, string photoId)
{
List<string> photoIds = GetAllPhotoIds(captureType);
if (photoIds.Remove(photoId))
{
SavePhotoIndex(captureType, photoIds);
}
}
private static void SavePhotoIndex(CaptureType captureType, List<string> 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<string> ids = new List<string>();
// Remove .png extension if present for decoration metadata filename
string baseId = photoId.Replace(".png", "");
return Path.Combine(GetCaptureDirectory(captureType), $"{baseId}_decorations.json");
}
#endregion