using System.Collections; using System.Collections.Generic; using Core; using Core.Lifecycle; using UnityEngine; using UnityEngine.UI; using Utils; 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 { public static StatuePhotoGalleryController Instance { get; private set; } [Header("Gallery UI")] [SerializeField] private Transform gridContainer; [SerializeField] private PhotoGridItem gridItemPrefab; [Header("Enlarged View")] [SerializeField] private Transform enlargedContainer; // Container for enlarged preview (top layer) [SerializeField] private GameObject backdrop; // Dark backdrop for enlarged view [SerializeField] private GameObject enlargedPreviewPrefab; // Prefab for enlarged preview (same as grid item) [Header("Pagination")] [SerializeField] private Button previousPageButton; [SerializeField] private Button nextPageButton; [SerializeField] private Text pageStatusText; private int currentPage; private List allPhotoIds = new List(); private Dictionary activeGridItems = new Dictionary(); private Dictionary thumbnailCache = new Dictionary(); private Dictionary fullPhotoCache = new Dictionary(); // Cache full photos for enlargement private Queue thumbnailCacheOrder = new Queue(); private bool isLoadingPage; private PhotoEnlargeController enlargeController; internal override void OnManagedAwake() { base.OnManagedAwake(); // Singleton pattern if (Instance != null && Instance != this) { Logging.Warning("[StatuePhotoGalleryController] Duplicate instance detected. Destroying duplicate."); Destroy(gameObject); return; } Instance = this; } internal override void OnManagedStart() { base.OnManagedStart(); // Wait for data manager to be ready before initializing DecorationDataManager.WhenReady(() => { InitializeGallery(); }); } /// /// Initialize gallery once data manager is ready /// private void InitializeGallery() { var settings = DecorationDataManager.Instance?.Settings; // Initialize enlarge controller enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, settings?.GalleryAnimationDuration ?? StatueDressupConstants.DefaultAnimationDuration); // Setup page navigation buttons if (previousPageButton != null) previousPageButton.onClick.AddListener(OnPreviousPageClicked); if (nextPageButton != null) nextPageButton.onClick.AddListener(OnNextPageClicked); // Hide backdrop initially if (backdrop != null) backdrop.SetActive(false); // Clear grid initially (in case there are leftover items from scene setup) ClearGrid(); // Load first page RefreshGallery(); } /// /// Refresh the entire gallery from scratch /// public void RefreshGallery() { // Clear existing items ClearGrid(); // Get all photo IDs allPhotoIds = PhotoManager.GetAllPhotoIds(CaptureType.StatueMinigame); currentPage = 0; Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {allPhotoIds.Count} photos"); // Display first page DisplayCurrentPage(); } /// /// Display the current page of photos (clears grid and shows only current page) /// private void DisplayCurrentPage() { if (isLoadingPage) return; isLoadingPage = true; // Clear current grid ClearGrid(); // Get photos for current page int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; List pagePhotoIds = PhotoManager.GetPhotoIdsPage(CaptureType.StatueMinigame, currentPage, itemsPerPage); Logging.Debug($"[StatuePhotoGalleryController] Displaying page {currentPage + 1}: {pagePhotoIds.Count} items"); // Spawn grid items for this page foreach (string photoId in pagePhotoIds) { SpawnGridItem(photoId); } // Update button states UpdatePageButtons(); // Update status text int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage); UpdateStatusText($"Page {currentPage + 1}/{totalPages} ({allPhotoIds.Count} photos)"); isLoadingPage = false; } /// /// Update page navigation button states /// private void UpdatePageButtons() { int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage); // Enable/disable previous button if (previousPageButton != null) { previousPageButton.interactable = currentPage > 0; } // Enable/disable next button if (nextPageButton != null) { nextPageButton.interactable = currentPage < totalPages - 1; } } /// /// Navigate to previous page /// private void OnPreviousPageClicked() { if (currentPage > 0) { currentPage--; DisplayCurrentPage(); Logging.Debug($"[StatuePhotoGalleryController] Navigated to previous page: {currentPage}"); } } /// /// Navigate to next page /// private void OnNextPageClicked() { int itemsPerPage = DecorationDataManager.Instance?.Settings?.GalleryItemsPerPage ?? StatueDressupConstants.DefaultGalleryItemsPerPage; int totalPages = Mathf.CeilToInt((float)allPhotoIds.Count / itemsPerPage); if (currentPage < totalPages - 1) { currentPage++; DisplayCurrentPage(); Logging.Debug($"[StatuePhotoGalleryController] Navigated to next page: {currentPage}"); } } /// /// 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 = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId); if (fullPhoto == null) { Logging.Warning($"[StatuePhotoGalleryController] Failed to load photo: {photoId}"); yield break; } // Create thumbnail int thumbSize = DecorationDataManager.Instance?.Settings?.GalleryThumbnailSize ?? StatueDressupConstants.DefaultThumbnailSize; Texture2D thumbnail = PhotoManager.CreateThumbnail(fullPhoto, thumbSize); // 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 int maxCached = DecorationDataManager.Instance?.Settings?.GalleryMaxCachedThumbnails ?? StatueDressupConstants.DefaultMaxCachedThumbnails; while (thumbnailCache.Count > maxCached && 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}"); } } } /// /// Enlarge a photo (called by PhotoGridItem) /// public void OnGridItemClicked(PhotoGridItem gridItem, string photoId) { if (enlargeController == null) { Logging.Error("[StatuePhotoGalleryController] Enlarge controller not initialized"); return; } // If already enlarged, shrink it if (enlargeController.IsPhotoEnlarged) { enlargeController.ShrinkPhoto(); return; } Logging.Debug($"[StatuePhotoGalleryController] Enlarging photo: {photoId}"); float enlargedScale = DecorationDataManager.Instance?.Settings?.GalleryEnlargedScale ?? StatueDressupConstants.DefaultEnlargedScale; // Check cache first if (fullPhotoCache.TryGetValue(photoId, out Texture2D fullPhoto)) { // Use cached photo enlargeController.EnlargePhoto(gridItem, enlargedPreviewPrefab != null ? enlargedPreviewPrefab : gridItem.gameObject, fullPhoto, enlargedScale); } else { // Load full-size photo fullPhoto = PhotoManager.LoadPhoto(CaptureType.StatueMinigame, photoId); if (fullPhoto == null) { Logging.Error($"[StatuePhotoGalleryController] Failed to load photo: {photoId}"); return; } // Cache it (limited cache) if (fullPhotoCache.Count < 10) // Keep only recent 10 full photos { fullPhotoCache[photoId] = fullPhoto; } enlargeController.EnlargePhoto(gridItem, enlargedPreviewPrefab != null ? enlargedPreviewPrefab : gridItem.gameObject, fullPhoto, enlargedScale); } } /// /// Cleanup when gallery is closed /// public void CleanupGallery() { if (enlargeController != null) { enlargeController.Cleanup(); } // Clean up cached full photos foreach (var photo in fullPhotoCache.Values) { if (photo != null) { Destroy(photo); } } fullPhotoCache.Clear(); } /// /// Update status text /// private void UpdateStatusText(string message) { if (pageStatusText != null) pageStatusText.text = message; Logging.Debug($"[StatuePhotoGalleryController] Status: {message}"); } /// /// Clear only the grid items (used when switching pages) /// private void ClearGrid() { // Destroy grid items foreach (var gridItem in activeGridItems.Values) { if (gridItem != null) Destroy(gridItem.gameObject); } activeGridItems.Clear(); Logging.Debug("[StatuePhotoGalleryController] Grid cleared"); } /// /// Clear all grid items and cached data (full cleanup) /// private void ClearGallery() { ClearGrid(); // Clear thumbnail cache foreach (var thumbnail in thumbnailCache.Values) { if (thumbnail != null) Destroy(thumbnail); } thumbnailCache.Clear(); thumbnailCacheOrder.Clear(); Logging.Debug("[StatuePhotoGalleryController] Gallery fully cleared"); } internal override void OnManagedDestroy() { base.OnManagedDestroy(); // Singleton cleanup if (Instance == this) { Instance = null; } // Clean up cached textures ClearGallery(); CleanupGallery(); // Unsubscribe buttons if (previousPageButton != null) previousPageButton.onClick.RemoveListener(OnPreviousPageClicked); if (nextPageButton != null) nextPageButton.onClick.RemoveListener(OnNextPageClicked); } } }