using System.Collections; using System.Collections.Generic; using Core; using Core.Lifecycle; using Minigames.StatueDressup.Utils; using UnityEngine; using UnityEngine.UI; 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; [SerializeField] private ScrollRect scrollRect; [Header("Enlarged View")] [SerializeField] private GameObject enlargedViewPanel; [SerializeField] private Image enlargedPhotoImage; [SerializeField] private Button closeEnlargedButton; [SerializeField] private Button deletePhotoButton; [SerializeField] private Text photoInfoText; [Header("Pagination")] [SerializeField] private Button loadMoreButton; [SerializeField] private Text statusText; [Header("Settings")] [SerializeField] private int itemsPerPage = 20; [SerializeField] private int thumbnailSize = 256; [SerializeField] private int maxCachedThumbnails = 50; // Keep recent thumbnails in memory private int _currentPage = 0; private List _allPhotoIds = new List(); private Dictionary _activeGridItems = new Dictionary(); private Dictionary _thumbnailCache = new Dictionary(); private Queue _thumbnailCacheOrder = new Queue(); private string _currentEnlargedPhotoId = null; private Texture2D _currentEnlargedTexture = null; internal override void OnManagedStart() { base.OnManagedStart(); // Setup buttons if (closeEnlargedButton != null) closeEnlargedButton.onClick.AddListener(CloseEnlargedView); if (deletePhotoButton != null) deletePhotoButton.onClick.AddListener(DeleteCurrentPhoto); if (loadMoreButton != null) loadMoreButton.onClick.AddListener(LoadNextPage); // Hide enlarged view initially if (enlargedViewPanel != null) enlargedViewPanel.SetActive(false); // Load first page RefreshGallery(); } /// /// Refresh the entire gallery from scratch /// public void RefreshGallery() { // Clear existing items ClearGallery(); // Get all photo IDs _allPhotoIds = StatuePhotoManager.GetAllPhotoIds(); _currentPage = 0; Logging.Debug($"[StatuePhotoGalleryController] Gallery refreshed: {_allPhotoIds.Count} photos"); // Load first page LoadNextPage(); } /// /// Load next page of photos /// private void LoadNextPage() { List pagePhotoIds = StatuePhotoManager.GetPhotoIdsPage(_currentPage, itemsPerPage); if (pagePhotoIds.Count == 0) { if (loadMoreButton != null) loadMoreButton.gameObject.SetActive(false); UpdateStatusText($"All photos loaded ({_allPhotoIds.Count} total)"); return; } Logging.Debug($"[StatuePhotoGalleryController] Loading page {_currentPage}: {pagePhotoIds.Count} items"); // Spawn grid items for this page foreach (string photoId in pagePhotoIds) { SpawnGridItem(photoId); } _currentPage++; // Update UI state bool hasMore = _currentPage * itemsPerPage < _allPhotoIds.Count; if (loadMoreButton != null) loadMoreButton.gameObject.SetActive(hasMore); UpdateStatusText($"Showing {_activeGridItems.Count} of {_allPhotoIds.Count} photos"); } /// /// 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 = StatuePhotoManager.LoadPhoto(photoId); if (fullPhoto == null) { Logging.Warning($"[StatuePhotoGalleryController] Failed to load photo: {photoId}"); yield break; } // Create thumbnail Texture2D thumbnail = StatuePhotoManager.CreateThumbnail(fullPhoto, thumbnailSize); // 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 while (_thumbnailCache.Count > maxCachedThumbnails && _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}"); } } } /// /// Show enlarged view of a photo (called by PhotoGridItem) /// public void ShowEnlargedView(string photoId) { if (enlargedViewPanel == null || enlargedPhotoImage == null) { Logging.Warning("[StatuePhotoGalleryController] Enlarged view UI not configured"); return; } Logging.Debug($"[StatuePhotoGalleryController] Showing enlarged view: {photoId}"); // Clear previous enlarged texture if (_currentEnlargedTexture != null) { Destroy(_currentEnlargedTexture); _currentEnlargedTexture = null; } // Load full-size photo _currentEnlargedTexture = StatuePhotoManager.LoadPhoto(photoId); if (_currentEnlargedTexture == null) { Logging.Error($"[StatuePhotoGalleryController] Failed to load enlarged photo: {photoId}"); return; } // Create sprite from texture Sprite enlargedSprite = Sprite.Create( _currentEnlargedTexture, new Rect(0, 0, _currentEnlargedTexture.width, _currentEnlargedTexture.height), new Vector2(0.5f, 0.5f) ); enlargedPhotoImage.sprite = enlargedSprite; _currentEnlargedPhotoId = photoId; // Update photo info UpdatePhotoInfo(photoId); // Show panel enlargedViewPanel.SetActive(true); } /// /// Close enlarged view /// private void CloseEnlargedView() { if (enlargedViewPanel != null) enlargedViewPanel.SetActive(false); // Clean up texture if (_currentEnlargedTexture != null) { Destroy(_currentEnlargedTexture); _currentEnlargedTexture = null; } _currentEnlargedPhotoId = null; } /// /// Delete currently viewed photo /// private void DeleteCurrentPhoto() { if (string.IsNullOrEmpty(_currentEnlargedPhotoId)) { Logging.Warning("[StatuePhotoGalleryController] No photo selected for deletion"); return; } string photoIdToDelete = _currentEnlargedPhotoId; // Close enlarged view first CloseEnlargedView(); // Delete photo bool deleted = StatuePhotoManager.DeletePhoto(photoIdToDelete); if (deleted) { // Remove from grid if (_activeGridItems.TryGetValue(photoIdToDelete, out PhotoGridItem gridItem)) { Destroy(gridItem.gameObject); _activeGridItems.Remove(photoIdToDelete); } // Remove from cache if (_thumbnailCache.TryGetValue(photoIdToDelete, out Texture2D thumbnail)) { Destroy(thumbnail); _thumbnailCache.Remove(photoIdToDelete); } // Refresh photo list _allPhotoIds.Remove(photoIdToDelete); UpdateStatusText($"Photo deleted. {_allPhotoIds.Count} photos remaining"); Logging.Debug($"[StatuePhotoGalleryController] Photo deleted: {photoIdToDelete}"); } } /// /// Update photo info text in enlarged view /// private void UpdatePhotoInfo(string photoId) { if (photoInfoText == null) return; StatuePhotoManager.PhotoMetadata metadata = StatuePhotoManager.GetPhotoMetadata(photoId); if (metadata != null) { System.DateTime timestamp = System.DateTime.Parse(metadata.timestamp); string dateStr = timestamp.ToString("MMM dd, yyyy hh:mm tt"); float fileSizeMB = metadata.fileSizeBytes / (1024f * 1024f); photoInfoText.text = $"Date: {dateStr}\n" + $"Decorations: {metadata.decorationCount}\n" + $"Size: {fileSizeMB:F2} MB"; } else { photoInfoText.text = "Photo information unavailable"; } } /// /// Update status text /// private void UpdateStatusText(string message) { if (statusText != null) statusText.text = message; Logging.Debug($"[StatuePhotoGalleryController] Status: {message}"); } /// /// Clear all grid items and cached data /// private void ClearGallery() { // Destroy grid items foreach (var gridItem in _activeGridItems.Values) { if (gridItem != null) Destroy(gridItem.gameObject); } _activeGridItems.Clear(); // Clear thumbnail cache foreach (var thumbnail in _thumbnailCache.Values) { if (thumbnail != null) Destroy(thumbnail); } _thumbnailCache.Clear(); _thumbnailCacheOrder.Clear(); Logging.Debug("[StatuePhotoGalleryController] Gallery cleared"); } internal override void OnManagedDestroy() { base.OnManagedDestroy(); // Cleanup ClearGallery(); CloseEnlargedView(); // Unsubscribe buttons if (closeEnlargedButton != null) closeEnlargedButton.onClick.RemoveListener(CloseEnlargedView); if (deletePhotoButton != null) deletePhotoButton.onClick.RemoveListener(DeleteCurrentPhoto); if (loadMoreButton != null) loadMoreButton.onClick.RemoveListener(LoadNextPage); } } }