Fix method signatures for apple machines
This commit is contained in:
@@ -1560,6 +1560,53 @@ Transform:
|
|||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &1685271989
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1685271991}
|
||||||
|
- component: {fileID: 1685271990}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: TestController
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &1685271990
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1685271989}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: deab1758ddef4bdea0e2c50554eaf568, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: AppleHillsScripts::Minigames.StatueDressup.Controllers.PhotoCaptureTestController
|
||||||
|
captureArea: {fileID: 65358845}
|
||||||
|
captureButton: {fileID: 37633367}
|
||||||
|
hideTheseObjects: []
|
||||||
|
--- !u!4 &1685271991
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1685271989}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -29.79184, y: 0.56528, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &2071711337
|
--- !u!1 &2071711337
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1691,3 +1738,4 @@ SceneRoots:
|
|||||||
- {fileID: 1217454518}
|
- {fileID: 1217454518}
|
||||||
- {fileID: 1126329096}
|
- {fileID: 1126329096}
|
||||||
- {fileID: 483064112}
|
- {fileID: 483064112}
|
||||||
|
- {fileID: 1685271991}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
"OptimizedRope",
|
"OptimizedRope",
|
||||||
"AudioSourceEvents",
|
"AudioSourceEvents",
|
||||||
"NewAssembly",
|
"NewAssembly",
|
||||||
"Unity.Cinemachine"
|
"Unity.Cinemachine",
|
||||||
|
"ScreenshotHelper",
|
||||||
|
"SwanDevCommon"
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ namespace Core.SaveLoad
|
|||||||
/// Has this state machine been restored from save data?
|
/// Has this state machine been restored from save data?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasBeenRestored { get; private set; }
|
public bool HasBeenRestored { get; private set; }
|
||||||
|
|
||||||
// Override ChangeState to call OnEnterState on SaveableState components
|
// Override ChangeState to call OnEnterState on SaveableState components
|
||||||
public new GameObject ChangeState(GameObject state)
|
public new void ChangeState(GameObject state)
|
||||||
{
|
{
|
||||||
var result = base.ChangeState(state);
|
var result = base.ChangeState(state);
|
||||||
|
|
||||||
@@ -41,11 +41,9 @@ namespace Core.SaveLoad
|
|||||||
saveableState.OnEnterState();
|
saveableState.OnEnterState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public new GameObject ChangeState(string state)
|
public new void ChangeState(string state)
|
||||||
{
|
{
|
||||||
var result = base.ChangeState(state);
|
var result = base.ChangeState(state);
|
||||||
|
|
||||||
@@ -58,11 +56,9 @@ namespace Core.SaveLoad
|
|||||||
saveableState.OnEnterState();
|
saveableState.OnEnterState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public new GameObject ChangeState(int childIndex)
|
public new void ChangeState(int childIndex)
|
||||||
{
|
{
|
||||||
var result = base.ChangeState(childIndex);
|
var result = base.ChangeState(childIndex);
|
||||||
|
|
||||||
@@ -75,8 +71,6 @@ namespace Core.SaveLoad
|
|||||||
saveableState.OnEnterState();
|
saveableState.OnEnterState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using Core;
|
||||||
|
using Minigames.StatueDressup.Utils;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimal test for photo capture - just capture and save to disk
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoCaptureTestController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Required")]
|
||||||
|
[SerializeField] private RectTransform captureArea;
|
||||||
|
[SerializeField] private Button captureButton;
|
||||||
|
|
||||||
|
[Header("Optional - UI to hide during capture")]
|
||||||
|
[SerializeField] private GameObject[] hideTheseObjects;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (captureButton != null)
|
||||||
|
{
|
||||||
|
captureButton.onClick.AddListener(OnCaptureClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[PhotoCaptureTest] Ready. Photo save path: {StatuePhotoManager.GetPhotoDirectory()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCaptureClicked()
|
||||||
|
{
|
||||||
|
if (captureArea == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[PhotoCaptureTest] Capture Area not assigned!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("[PhotoCaptureTest] Starting capture...");
|
||||||
|
StartCoroutine(CaptureCoroutine());
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Collections.IEnumerator CaptureCoroutine()
|
||||||
|
{
|
||||||
|
// Hide UI
|
||||||
|
foreach (var obj in hideTheseObjects)
|
||||||
|
if (obj != null) obj.SetActive(false);
|
||||||
|
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
|
// Capture
|
||||||
|
bool done = false;
|
||||||
|
Texture2D photo = null;
|
||||||
|
|
||||||
|
StatuePhotoManager.CaptureAreaPhoto(captureArea, (texture) => {
|
||||||
|
photo = texture;
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
yield return new WaitUntil(() => done);
|
||||||
|
|
||||||
|
// Restore UI
|
||||||
|
foreach (var obj in hideTheseObjects)
|
||||||
|
if (obj != null) obj.SetActive(true);
|
||||||
|
|
||||||
|
// Save
|
||||||
|
if (photo != null)
|
||||||
|
{
|
||||||
|
string photoId = StatuePhotoManager.SavePhoto(photo, 0);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(photoId))
|
||||||
|
{
|
||||||
|
string path = $"{StatuePhotoManager.GetPhotoDirectory()}/{photoId}.png";
|
||||||
|
Debug.Log($"[PhotoCaptureTest] ✅ SUCCESS! Photo saved: {path}");
|
||||||
|
Debug.Log($"[PhotoCaptureTest] Photo size: {photo.width}x{photo.height}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("[PhotoCaptureTest] ❌ Failed to save photo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("[PhotoCaptureTest] ❌ Failed to capture photo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (captureButton != null)
|
||||||
|
captureButton.onClick.RemoveListener(OnCaptureClicked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: deab1758ddef4bdea0e2c50554eaf568
|
||||||
|
timeCreated: 1764077035
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Individual photo thumbnail in the gallery grid.
|
||||||
|
/// Handles click to show enlarged view.
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoGridItem : MonoBehaviour, IPointerClickHandler
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private Image thumbnailImage;
|
||||||
|
[SerializeField] private GameObject loadingIndicator;
|
||||||
|
|
||||||
|
private string _photoId;
|
||||||
|
private StatuePhotoGalleryController _galleryController;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize grid item with photo ID
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(string photoId, StatuePhotoGalleryController controller)
|
||||||
|
{
|
||||||
|
_photoId = photoId;
|
||||||
|
_galleryController = controller;
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (loadingIndicator != null)
|
||||||
|
loadingIndicator.SetActive(true);
|
||||||
|
|
||||||
|
if (thumbnailImage != null)
|
||||||
|
thumbnailImage.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the thumbnail texture
|
||||||
|
/// </summary>
|
||||||
|
public void SetThumbnail(Texture2D thumbnail)
|
||||||
|
{
|
||||||
|
if (thumbnail == null)
|
||||||
|
{
|
||||||
|
Logging.Warning($"[PhotoGridItem] Null thumbnail for photo: {_photoId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sprite from thumbnail
|
||||||
|
Sprite thumbnailSprite = Sprite.Create(
|
||||||
|
thumbnail,
|
||||||
|
new Rect(0, 0, thumbnail.width, thumbnail.height),
|
||||||
|
new Vector2(0.5f, 0.5f)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (thumbnailImage != null)
|
||||||
|
{
|
||||||
|
thumbnailImage.sprite = thumbnailSprite;
|
||||||
|
thumbnailImage.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading indicator
|
||||||
|
if (loadingIndicator != null)
|
||||||
|
loadingIndicator.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle click to show enlarged view
|
||||||
|
/// </summary>
|
||||||
|
public void OnPointerClick(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (_galleryController != null && !string.IsNullOrEmpty(_photoId))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[PhotoGridItem] Clicked: {_photoId}");
|
||||||
|
_galleryController.ShowEnlargedView(_photoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: acd8d97ee2f744d984a9507e75309be0
|
||||||
|
timeCreated: 1764065014
|
||||||
@@ -129,82 +129,61 @@ namespace Minigames.StatueDressup.Controllers
|
|||||||
{
|
{
|
||||||
yield return new WaitForEndOfFrame();
|
yield return new WaitForEndOfFrame();
|
||||||
|
|
||||||
// Capture the photo area
|
// Capture using Screenshot Helper via StatuePhotoManager
|
||||||
Texture2D photo = CaptureScreenshotArea();
|
bool captureComplete = false;
|
||||||
|
Texture2D capturedPhoto = null;
|
||||||
|
|
||||||
if (photo != null)
|
Utils.StatuePhotoManager.CaptureAreaPhoto(
|
||||||
|
photoArea,
|
||||||
|
(Texture2D texture) =>
|
||||||
|
{
|
||||||
|
capturedPhoto = texture;
|
||||||
|
captureComplete = true;
|
||||||
|
},
|
||||||
|
Camera.main
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for capture to complete
|
||||||
|
yield return new WaitUntil(() => captureComplete);
|
||||||
|
|
||||||
|
if (capturedPhoto != null)
|
||||||
{
|
{
|
||||||
// Save photo to album
|
// Save photo with StatuePhotoManager
|
||||||
SavePhotoToAlbum(photo);
|
int decorationCount = _placedDecorations.Count;
|
||||||
|
string photoId = Utils.StatuePhotoManager.SavePhoto(capturedPhoto, decorationCount);
|
||||||
|
|
||||||
// Award cards
|
if (!string.IsNullOrEmpty(photoId))
|
||||||
AwardCards();
|
{
|
||||||
|
Logging.Debug($"[StatueDecorationController] Photo saved: {photoId}");
|
||||||
// Update town icon
|
|
||||||
UpdateTownIcon(photo);
|
// Award cards
|
||||||
|
AwardCards();
|
||||||
// Show completion feedback
|
|
||||||
ShowCompletionFeedback();
|
// Update town icon
|
||||||
|
UpdateTownIcon(capturedPhoto);
|
||||||
_minigameCompleted = true;
|
|
||||||
|
// Show completion feedback
|
||||||
|
ShowCompletionFeedback();
|
||||||
|
|
||||||
|
_minigameCompleted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error("[StatueDecorationController] Failed to save photo!");
|
||||||
|
DebugUIMessage.Show("Failed to save photo!", Color.red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error("[StatueDecorationController] Failed to capture photo!");
|
||||||
|
DebugUIMessage.Show("Failed to capture photo!", Color.red);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore UI
|
// Restore UI
|
||||||
HideUIForPhoto(false);
|
HideUIForPhoto(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Capture screenshot of specific area
|
|
||||||
/// </summary>
|
|
||||||
private Texture2D CaptureScreenshotArea()
|
|
||||||
{
|
|
||||||
if (photoArea == null)
|
|
||||||
{
|
|
||||||
Logging.Warning("[StatueDecorationController] No photo area specified, capturing full screen");
|
|
||||||
|
|
||||||
// Capture full screen
|
|
||||||
Texture2D screenshot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
|
||||||
screenshot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
|
||||||
screenshot.Apply();
|
|
||||||
return screenshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get world corners of the rect
|
|
||||||
Vector3[] corners = new Vector3[4];
|
|
||||||
photoArea.GetWorldCorners(corners);
|
|
||||||
|
|
||||||
// Convert to screen space
|
|
||||||
Vector2 min = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[0]);
|
|
||||||
Vector2 max = RectTransformUtility.WorldToScreenPoint(Camera.main, corners[2]);
|
|
||||||
|
|
||||||
int width = (int)(max.x - min.x);
|
|
||||||
int height = (int)(max.y - min.y);
|
|
||||||
|
|
||||||
Logging.Debug($"[StatueDecorationController] Capturing area: {width}x{height} at ({min.x}, {min.y})");
|
|
||||||
|
|
||||||
// Capture the specified area
|
|
||||||
Texture2D areaScreenshot = new Texture2D(width, height, TextureFormat.RGB24, false);
|
|
||||||
areaScreenshot.ReadPixels(new Rect(min.x, min.y, width, height), 0, 0);
|
|
||||||
areaScreenshot.Apply();
|
|
||||||
|
|
||||||
return areaScreenshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save photo to card album
|
|
||||||
/// </summary>
|
|
||||||
private void SavePhotoToAlbum(Texture2D photo)
|
|
||||||
{
|
|
||||||
// TODO: Integrate with existing album save system
|
|
||||||
// For now, save to PlayerPrefs as base64
|
|
||||||
byte[] bytes = photo.EncodeToPNG();
|
|
||||||
string base64 = System.Convert.ToBase64String(bytes);
|
|
||||||
string saveKey = _settings?.PhotoSaveKey ?? photoSaveKey;
|
|
||||||
PlayerPrefs.SetString(saveKey, base64);
|
|
||||||
PlayerPrefs.Save();
|
|
||||||
|
|
||||||
Logging.Debug("[StatueDecorationController] Photo saved to album");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Award Blokkemon cards to player
|
/// Award Blokkemon cards to player
|
||||||
|
|||||||
@@ -0,0 +1,394 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a7339274a0c54f8c9134942f84d47140
|
||||||
|
timeCreated: 1764065004
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using SDev;
|
||||||
|
|
||||||
|
namespace Minigames.StatueDressup.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages statue photo capture, storage, and retrieval.
|
||||||
|
/// Supports area-limited screenshots, persistent file storage (mobile + PC),
|
||||||
|
/// and optimized gallery loading with pagination.
|
||||||
|
/// </summary>
|
||||||
|
public static class StatuePhotoManager
|
||||||
|
{
|
||||||
|
private const string PHOTO_FOLDER = "StatuePhotos";
|
||||||
|
private const string PHOTO_PREFIX = "MrCementStatue_";
|
||||||
|
private const string METADATA_KEY_PREFIX = "StatuePhoto_Meta_";
|
||||||
|
private const string PHOTO_INDEX_KEY = "StatuePhoto_Index";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Photo metadata stored in PlayerPrefs
|
||||||
|
/// </summary>
|
||||||
|
[System.Serializable]
|
||||||
|
public class PhotoMetadata
|
||||||
|
{
|
||||||
|
public string photoId;
|
||||||
|
public string timestamp;
|
||||||
|
public int decorationCount;
|
||||||
|
public long fileSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Capture
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture a specific area of the screen using Screenshot Helper
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="captureArea">RectTransform defining the capture region</param>
|
||||||
|
/// <param name="onComplete">Callback with captured Texture2D</param>
|
||||||
|
/// <param name="mainCamera">Camera used for coordinate conversion (null = Camera.main)</param>
|
||||||
|
public static void CaptureAreaPhoto(RectTransform captureArea, Action<Texture2D> onComplete, Camera mainCamera = null)
|
||||||
|
{
|
||||||
|
if (captureArea == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[StatuePhotoManager] CaptureArea RectTransform is null!");
|
||||||
|
onComplete?.Invoke(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainCamera == null) mainCamera = Camera.main;
|
||||||
|
|
||||||
|
// Get screen rect from RectTransform
|
||||||
|
Rect screenRect = GetScreenRectFromRectTransform(captureArea, mainCamera);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
|
||||||
|
|
||||||
|
// Use Screenshot Helper's Capture method
|
||||||
|
ScreenshotHelper.Instance.Capture(
|
||||||
|
screenRect.position,
|
||||||
|
screenRect.size,
|
||||||
|
(Texture2D texture) =>
|
||||||
|
{
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatuePhotoManager] Photo captured: {texture.width}x{texture.height}");
|
||||||
|
onComplete?.Invoke(texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error("[StatuePhotoManager] Screenshot Helper returned null texture!");
|
||||||
|
onComplete?.Invoke(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert RectTransform world corners to screen space rect
|
||||||
|
/// </summary>
|
||||||
|
private static Rect GetScreenRectFromRectTransform(RectTransform rectTransform, Camera camera)
|
||||||
|
{
|
||||||
|
Vector3[] corners = new Vector3[4];
|
||||||
|
rectTransform.GetWorldCorners(corners);
|
||||||
|
|
||||||
|
Vector2 min = RectTransformUtility.WorldToScreenPoint(camera, corners[0]);
|
||||||
|
Vector2 max = RectTransformUtility.WorldToScreenPoint(camera, corners[2]);
|
||||||
|
|
||||||
|
// Ensure positive dimensions
|
||||||
|
float width = Mathf.Abs(max.x - min.x);
|
||||||
|
float height = Mathf.Abs(max.y - min.y);
|
||||||
|
|
||||||
|
return new Rect(min.x, min.y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Save/Load
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save photo to persistent storage with metadata
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="photo">Texture2D to save</param>
|
||||||
|
/// <param name="decorationCount">Number of decorations placed (for metadata)</param>
|
||||||
|
/// <returns>Photo ID if successful, null if failed</returns>
|
||||||
|
public static string SavePhoto(Texture2D photo, int decorationCount = 0)
|
||||||
|
{
|
||||||
|
if (photo == null)
|
||||||
|
{
|
||||||
|
Logging.Error("[StatuePhotoManager] Cannot save null photo");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Generate unique photo ID
|
||||||
|
string photoId = $"{PHOTO_PREFIX}{DateTime.Now.Ticks}";
|
||||||
|
|
||||||
|
// Save texture using FileSaveUtil
|
||||||
|
string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG(
|
||||||
|
photo,
|
||||||
|
FileSaveUtil.AppPath.PersistentDataPath,
|
||||||
|
PHOTO_FOLDER,
|
||||||
|
photoId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate file size
|
||||||
|
FileInfo fileInfo = new FileInfo(savedPath);
|
||||||
|
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
|
||||||
|
|
||||||
|
// Save metadata
|
||||||
|
PhotoMetadata metadata = new PhotoMetadata
|
||||||
|
{
|
||||||
|
photoId = photoId,
|
||||||
|
timestamp = DateTime.Now.ToString("o"),
|
||||||
|
decorationCount = decorationCount,
|
||||||
|
fileSizeBytes = fileSize
|
||||||
|
};
|
||||||
|
|
||||||
|
SaveMetadata(metadata);
|
||||||
|
AddToPhotoIndex(photoId);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatuePhotoManager] Photo saved: {savedPath} ({fileSize} bytes)");
|
||||||
|
return photoId;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[StatuePhotoManager] Failed to save photo: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load photo texture from storage
|
||||||
|
/// </summary>
|
||||||
|
public static Texture2D LoadPhoto(string photoId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(photoId))
|
||||||
|
{
|
||||||
|
Logging.Warning("[StatuePhotoManager] PhotoId is null or empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string filePath = GetPhotoFilePath(photoId);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
Logging.Warning($"[StatuePhotoManager] Photo not found: {filePath}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileData = File.ReadAllBytes(filePath);
|
||||||
|
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
||||||
|
|
||||||
|
if (texture.LoadImage(fileData))
|
||||||
|
{
|
||||||
|
Logging.Debug($"[StatuePhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})");
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Error($"[StatuePhotoManager] Failed to decode image: {photoId}");
|
||||||
|
UnityEngine.Object.Destroy(texture);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[StatuePhotoManager] Failed to load photo {photoId}: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete photo and its metadata
|
||||||
|
/// </summary>
|
||||||
|
public static bool DeletePhoto(string photoId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(photoId)) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string filePath = GetPhotoFilePath(photoId);
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteMetadata(photoId);
|
||||||
|
RemoveFromPhotoIndex(photoId);
|
||||||
|
|
||||||
|
Logging.Debug($"[StatuePhotoManager] Photo deleted: {photoId}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error($"[StatuePhotoManager] Failed to delete photo {photoId}: {e.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Gallery Support
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all photo IDs sorted by timestamp (newest first)
|
||||||
|
/// </summary>
|
||||||
|
public static List<string> GetAllPhotoIds()
|
||||||
|
{
|
||||||
|
string indexJson = PlayerPrefs.GetString(PHOTO_INDEX_KEY, "[]");
|
||||||
|
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(a);
|
||||||
|
PhotoMetadata metaB = LoadMetadata(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get paginated photo IDs for optimized gallery loading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page number (0-indexed)</param>
|
||||||
|
/// <param name="pageSize">Number of items per page</param>
|
||||||
|
public static List<string> GetPhotoIdsPage(int page, int pageSize)
|
||||||
|
{
|
||||||
|
List<string> allIds = GetAllPhotoIds();
|
||||||
|
int startIndex = page * pageSize;
|
||||||
|
|
||||||
|
if (startIndex >= allIds.Count) return new List<string>();
|
||||||
|
|
||||||
|
int count = Mathf.Min(pageSize, allIds.Count - startIndex);
|
||||||
|
return allIds.GetRange(startIndex, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get total number of saved photos
|
||||||
|
/// </summary>
|
||||||
|
public static int GetPhotoCount()
|
||||||
|
{
|
||||||
|
return GetAllPhotoIds().Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get latest photo ID (most recent)
|
||||||
|
/// </summary>
|
||||||
|
public static string GetLatestPhotoId()
|
||||||
|
{
|
||||||
|
List<string> allIds = GetAllPhotoIds();
|
||||||
|
return allIds.Count > 0 ? allIds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load photo metadata
|
||||||
|
/// </summary>
|
||||||
|
public static PhotoMetadata GetPhotoMetadata(string photoId)
|
||||||
|
{
|
||||||
|
return LoadMetadata(photoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create thumbnail from full-size photo (for gallery preview)
|
||||||
|
/// </summary>
|
||||||
|
public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256)
|
||||||
|
{
|
||||||
|
if (fullSizePhoto == null) return null;
|
||||||
|
|
||||||
|
int width = fullSizePhoto.width;
|
||||||
|
int height = fullSizePhoto.height;
|
||||||
|
|
||||||
|
// Calculate thumbnail size maintaining aspect ratio
|
||||||
|
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
|
||||||
|
int thumbWidth = Mathf.RoundToInt(width * scale);
|
||||||
|
int thumbHeight = Mathf.RoundToInt(height * scale);
|
||||||
|
|
||||||
|
// Create thumbnail using bilinear filtering
|
||||||
|
RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32);
|
||||||
|
RenderTexture.active = rt;
|
||||||
|
|
||||||
|
Graphics.Blit(fullSizePhoto, rt);
|
||||||
|
|
||||||
|
Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false);
|
||||||
|
thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0);
|
||||||
|
thumbnail.Apply();
|
||||||
|
|
||||||
|
RenderTexture.active = null;
|
||||||
|
RenderTexture.ReleaseTemporary(rt);
|
||||||
|
|
||||||
|
return thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Helpers
|
||||||
|
|
||||||
|
public static string GetPhotoDirectory()
|
||||||
|
{
|
||||||
|
return Path.Combine(Application.persistentDataPath, PHOTO_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPhotoFilePath(string photoId)
|
||||||
|
{
|
||||||
|
return Path.Combine(GetPhotoDirectory(), $"{photoId}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SaveMetadata(PhotoMetadata metadata)
|
||||||
|
{
|
||||||
|
string json = JsonUtility.ToJson(metadata);
|
||||||
|
PlayerPrefs.SetString(METADATA_KEY_PREFIX + metadata.photoId, json);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PhotoMetadata LoadMetadata(string photoId)
|
||||||
|
{
|
||||||
|
string json = PlayerPrefs.GetString(METADATA_KEY_PREFIX + photoId, null);
|
||||||
|
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteMetadata(string photoId)
|
||||||
|
{
|
||||||
|
PlayerPrefs.DeleteKey(METADATA_KEY_PREFIX + photoId);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddToPhotoIndex(string photoId)
|
||||||
|
{
|
||||||
|
List<string> photoIds = GetAllPhotoIds();
|
||||||
|
if (!photoIds.Contains(photoId))
|
||||||
|
{
|
||||||
|
photoIds.Add(photoId);
|
||||||
|
SavePhotoIndex(photoIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveFromPhotoIndex(string photoId)
|
||||||
|
{
|
||||||
|
List<string> photoIds = GetAllPhotoIds();
|
||||||
|
if (photoIds.Remove(photoId))
|
||||||
|
{
|
||||||
|
SavePhotoIndex(photoIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SavePhotoIndex(List<string> photoIds)
|
||||||
|
{
|
||||||
|
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
|
||||||
|
PlayerPrefs.SetString(PHOTO_INDEX_KEY, json);
|
||||||
|
PlayerPrefs.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WrapJsonArray(string json)
|
||||||
|
{
|
||||||
|
if (json.StartsWith("[")) return "{\"ids\":" + json + "}";
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
private class PhotoIdList
|
||||||
|
{
|
||||||
|
public List<string> ids = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f3e9a2b4c5d6e7f8a9b0c1d2e3f4a5b
|
||||||
Reference in New Issue
Block a user