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 { [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 AppleHills.Core.Settings.IStatueDressupSettings _settings; 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 OnManagedStart() { base.OnManagedStart(); // Get settings _settings = GameManager.GetSettingsObject(); // Initialize enlarge controller _enlargeController = new PhotoEnlargeController(backdrop, enlargedContainer, _settings?.GalleryAnimationDuration ?? 0.3f); // 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 = _settings?.GalleryItemsPerPage ?? 20; 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 = _settings?.GalleryItemsPerPage ?? 20; 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 = _settings?.GalleryItemsPerPage ?? 20; 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 = _settings?.GalleryThumbnailSize ?? 256; 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 = _settings?.GalleryMaxCachedThumbnails ?? 50; 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 = _settings?.GalleryEnlargedScale ?? 2.5f; // 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(); // Cleanup ClearGallery(); CleanupGallery(); // Unsubscribe buttons if (previousPageButton != null) previousPageButton.onClick.RemoveListener(OnPreviousPageClicked); if (nextPageButton != null) nextPageButton.onClick.RemoveListener(OnNextPageClicked); } } }