feat: SongSelect panel — NAS song list with download/play in Menu.unity
- Add DownloadManager, SongLibrary, SongSelectManager, SongDetailPanel scripts - Rebuild SongSelect panel inside Menu.unity using VRBeatsKit style: left scroll list (ALL/OWNED tabs) + right detail panel (diff buttons, Download/Delete/Play) - SceneBuilder replaced: only ③ Menu — Rebuild SongSelect Panel remains - All UI text in English Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
public class SongLibrary : MonoBehaviour
|
||||
{
|
||||
public static SongLibrary Instance { get; private set; }
|
||||
|
||||
private const string FileName = "song_library.json";
|
||||
private static string SavePath => Path.Combine(Application.persistentDataPath, FileName);
|
||||
|
||||
private LibraryData _data = new LibraryData();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
Load();
|
||||
}
|
||||
|
||||
// ── Public API ───────────────────────────────────────────
|
||||
|
||||
public void MarkDownloaded(string songId, string difficulty)
|
||||
{
|
||||
LibraryEntry entry = GetOrCreate(songId);
|
||||
if (!entry.difficulties.Contains(difficulty))
|
||||
entry.difficulties.Add(difficulty);
|
||||
entry.lastAccessedAt = DateTime.UtcNow.ToString("o");
|
||||
Save();
|
||||
}
|
||||
|
||||
public void MarkDifficultyRemoved(string songId, string difficulty)
|
||||
{
|
||||
LibraryEntry entry = Find(songId);
|
||||
if (entry == null) return;
|
||||
|
||||
entry.difficulties.Remove(difficulty);
|
||||
if (entry.difficulties.Count == 0)
|
||||
_data.entries.Remove(entry);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void MarkSongRemoved(string songId)
|
||||
{
|
||||
_data.entries.RemoveAll(e => e.songId == songId);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void TouchSong(string songId)
|
||||
{
|
||||
LibraryEntry entry = Find(songId);
|
||||
if (entry == null) return;
|
||||
entry.lastAccessedAt = DateTime.UtcNow.ToString("o");
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool IsSongDownloaded(string songId)
|
||||
=> Find(songId) != null;
|
||||
|
||||
public bool IsDifficultyDownloaded(string songId, string difficulty)
|
||||
=> Find(songId)?.difficulties.Contains(difficulty) ?? false;
|
||||
|
||||
public List<LibraryEntry> GetAll()
|
||||
=> _data.entries;
|
||||
|
||||
public void ValidateWithFileSystem(DownloadManager dm, List<SongInfo> songs)
|
||||
{
|
||||
bool dirty = false;
|
||||
foreach (SongInfo song in songs)
|
||||
{
|
||||
LibraryEntry entry = Find(song.id);
|
||||
if (entry == null) continue;
|
||||
|
||||
if (!dm.IsSongDownloaded(song.id))
|
||||
{
|
||||
_data.entries.Remove(entry);
|
||||
dirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.difficulties.RemoveAll(d => !dm.IsDifficultyDownloaded(song, d));
|
||||
if (entry.difficulties.Count == 0)
|
||||
{
|
||||
_data.entries.Remove(entry);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if (dirty) Save();
|
||||
}
|
||||
|
||||
// ── 내부 구현 ─────────────────────────────────────────────
|
||||
|
||||
private LibraryEntry Find(string songId)
|
||||
=> _data.entries.Find(e => e.songId == songId);
|
||||
|
||||
private LibraryEntry GetOrCreate(string songId)
|
||||
{
|
||||
LibraryEntry entry = Find(songId);
|
||||
if (entry != null) return entry;
|
||||
entry = new LibraryEntry { songId = songId };
|
||||
_data.entries.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
if (!File.Exists(SavePath)) return;
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(SavePath);
|
||||
_data = JsonUtility.FromJson<LibraryData>(json) ?? new LibraryData();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[SongLibrary] 로드 실패, 초기화: {e.Message}");
|
||||
_data = new LibraryData();
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
File.WriteAllText(SavePath, JsonUtility.ToJson(_data, true));
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LibraryData
|
||||
{
|
||||
public List<LibraryEntry> entries = new List<LibraryEntry>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LibraryEntry
|
||||
{
|
||||
public string songId;
|
||||
public List<string> difficulties = new List<string>();
|
||||
public string lastAccessedAt;
|
||||
}
|
||||
Reference in New Issue
Block a user