395 lines
14 KiB
C#
395 lines
14 KiB
C#
|
|
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
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 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.
|
|||
|
|
/// </summary>
|
|||
|
|
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<string> _allPhotoIds = new List<string>();
|
|||
|
|
private Dictionary<string, PhotoGridItem> _activeGridItems = new Dictionary<string, PhotoGridItem>();
|
|||
|
|
private Dictionary<string, Texture2D> _thumbnailCache = new Dictionary<string, Texture2D>();
|
|||
|
|
private Queue<string> _thumbnailCacheOrder = new Queue<string>();
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Refresh the entire gallery from scratch
|
|||
|
|
/// </summary>
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Load next page of photos
|
|||
|
|
/// </summary>
|
|||
|
|
private void LoadNextPage()
|
|||
|
|
{
|
|||
|
|
List<string> 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");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Spawn a grid item for a photo
|
|||
|
|
/// </summary>
|
|||
|
|
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));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Load thumbnail for grid item (async to avoid frame hitches)
|
|||
|
|
/// </summary>
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Cache thumbnail with LRU eviction
|
|||
|
|
/// </summary>
|
|||
|
|
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}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Show enlarged view of a photo (called by PhotoGridItem)
|
|||
|
|
/// </summary>
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Close enlarged view
|
|||
|
|
/// </summary>
|
|||
|
|
private void CloseEnlargedView()
|
|||
|
|
{
|
|||
|
|
if (enlargedViewPanel != null)
|
|||
|
|
enlargedViewPanel.SetActive(false);
|
|||
|
|
|
|||
|
|
// Clean up texture
|
|||
|
|
if (_currentEnlargedTexture != null)
|
|||
|
|
{
|
|||
|
|
Destroy(_currentEnlargedTexture);
|
|||
|
|
_currentEnlargedTexture = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_currentEnlargedPhotoId = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Delete currently viewed photo
|
|||
|
|
/// </summary>
|
|||
|
|
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}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Update photo info text in enlarged view
|
|||
|
|
/// </summary>
|
|||
|
|
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";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Update status text
|
|||
|
|
/// </summary>
|
|||
|
|
private void UpdateStatusText(string message)
|
|||
|
|
{
|
|||
|
|
if (statusText != null)
|
|||
|
|
statusText.text = message;
|
|||
|
|
|
|||
|
|
Logging.Debug($"[StatuePhotoGalleryController] Status: {message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Clear all grid items and cached data
|
|||
|
|
/// </summary>
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|