395 lines
16 KiB
C#
395 lines
16 KiB
C#
#nullable enable
|
|
using Cysharp.Threading.Tasks;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UVC.Log;
|
|
using UVC.UI.Modal;
|
|
using UVC.Util;
|
|
|
|
namespace UVC.Factory.Playback.UI
|
|
{
|
|
/// <summary>
|
|
/// 📅 재생 목록(Playback List)을 날짜별로 보여주고, 원하는 시간대의 파일을 선택할 수 있는 모달 창입니다.
|
|
/// - 날짜 드롭다운, 시간별 리스트, 다운로드 상태, 선택/확인 버튼 등을 제공합니다.
|
|
///
|
|
/// <para>샘플 사용법:</para>
|
|
/// <code>
|
|
/// // 1. 모달을 띄우는 코드 예시
|
|
/// var content = new ModalContent(UIPlaybackListModal.PrefabPath)
|
|
/// {
|
|
/// Title = "Playback List",
|
|
/// ConfirmButtonText = "Play",
|
|
/// ShowCancelButton = false
|
|
/// };
|
|
/// // 모달을 열고, 사용자가 선택한 결과를 기다립니다.
|
|
/// var result = await Modal.Open<UIPlaybackListItemData>(content);
|
|
/// if (result != null)
|
|
/// {
|
|
/// Debug.Log($"선택된 파일: {result.date} {result.time}시");
|
|
/// }
|
|
/// </code>
|
|
/// </summary>
|
|
[RequireComponent(typeof(CanvasGroup))]
|
|
public class UIPlaybackListModal : ModalView
|
|
{
|
|
/// <summary>
|
|
/// 이 모달의 프리팹 경로(Resources 폴더 기준)
|
|
/// </summary>
|
|
public static readonly string PrefabPath = "Factory/Prefabs/Playback/UIPlaybackListModal";
|
|
|
|
/// <summary>
|
|
/// 프리팹에서 모달을 생성합니다.
|
|
/// </summary>
|
|
/// <param name="parent">부모 Transform (생략 가능)</param>
|
|
/// <returns>UIPlaybackListModal 인스턴스</returns>
|
|
/// <example>
|
|
/// var modal = UIPlaybackListModal.CreateFromPrefab(parentTransform);
|
|
/// </example>
|
|
public static UIPlaybackListModal CreateFromPrefab(Transform parent = null)
|
|
{
|
|
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
|
GameObject go = UnityEngine.Object.Instantiate(prefab);
|
|
UIPlaybackListModal modal = go.GetComponent<UIPlaybackListModal>();
|
|
return modal;
|
|
}
|
|
|
|
// 날짜별로 시간대 파일 목록을 저장하는 딕셔너리
|
|
private Dictionary<string, List<UIPlaybackListItemData>>? data;
|
|
|
|
/// <summary>
|
|
/// '확인' 버튼을 누를 수 있는지 여부
|
|
/// (다운로드 완료된 항목이 선택되어 있고, 현재 다운로드 중인 항목이 없을 때만 true)
|
|
/// </summary>
|
|
public bool IsOkable => (selectedItem != null && selectedItem.status == UIPlaybackListItemStatus.Downloaded && UIPlaybackListItem.DownloadingItems.Count == 0);
|
|
|
|
// 현재 선택된 아이템 데이터
|
|
private UIPlaybackListItemData? selectedItem = null;
|
|
|
|
[SerializeField]
|
|
private TMP_Dropdown dropdownDate; // 날짜 선택 드롭다운
|
|
[SerializeField]
|
|
private ScrollRect scrollRectTime; // 시간대 리스트 스크롤 영역
|
|
|
|
// 모달이 닫힐 때 반환할 결과 데이터
|
|
private UIPlaybackListItemData? resultData = null;
|
|
|
|
/// <summary>
|
|
/// 모달이 열릴 때 호출됩니다. (비동기)
|
|
/// </summary>
|
|
/// <param name="content">모달에 표시할 내용/설정</param>
|
|
public override async UniTask OnOpen(ModalContent content)
|
|
{
|
|
await base.OnOpen(content); // 부모의 OnOpen을 먼저 호출해서 기본 UI를 설정해요.
|
|
initContent();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모달이 닫힐 때 결과로 반환할 데이터를 돌려줍니다.
|
|
/// </summary>
|
|
/// <returns>선택된 UIPlaybackListItemData 또는 null</returns>
|
|
public override UniTask<object?> GetResult()
|
|
{
|
|
if (data != null) data.Clear();
|
|
data = null;
|
|
return UniTask.FromResult<object?>(resultData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모달이 닫힐 때 호출됩니다.
|
|
/// </summary>
|
|
public override async UniTask OnClose(ModalContent content)
|
|
{
|
|
await base.OnClose(content);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모달의 내용을 초기화합니다. (날짜/시간대 데이터 불러오기)
|
|
/// </summary>
|
|
private async void initContent()
|
|
{
|
|
confirmButton.interactable = false;
|
|
|
|
// 서버에서 날짜/시간대 목록을 받아옵니다.
|
|
Dictionary<string, Dictionary<string, string>>? data = await PlaybackService.Instance.RequestDataAsync();
|
|
dropdownDate.onValueChanged.AddListener(DropDownDateChanged);
|
|
LocalSetData(); // 로컬 파일 기준으로 먼저 리스트를 만듭니다.
|
|
if (data != null) SetData(data); // 서버 데이터가 있으면 반영합니다.
|
|
}
|
|
|
|
/// <summary>
|
|
/// 오브젝트가 파괴될 때 이벤트 해제 및 정리
|
|
/// </summary>
|
|
protected void OnDestroy()
|
|
{
|
|
dropdownDate.onValueChanged.RemoveListener(DropDownDateChanged);
|
|
UIPlaybackListItem.ClearAll();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 로컬 저장소에서 날짜/시간대 파일 목록을 읽어와 리스트를 만듭니다.
|
|
/// </summary>
|
|
public void LocalSetData()
|
|
{
|
|
Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>();
|
|
var dateList = new List<TMP_Dropdown.OptionData>();
|
|
|
|
string playbackPath = PlaybackService.PlaybackFolderPath;
|
|
DirectoryInfo di = new DirectoryInfo(playbackPath);
|
|
if (di.Exists)
|
|
{
|
|
var childDi = di.EnumerateDirectories();
|
|
foreach (var child in childDi)
|
|
{
|
|
string date = child.Name;
|
|
//Debug.Log($"date:{date}");
|
|
var childFiles = child.EnumerateFiles();
|
|
if (childFiles.Count() > 0 && !newData.ContainsKey(date)) newData[date] = new List<UIPlaybackListItemData>();
|
|
foreach (var childFile in childFiles)
|
|
{
|
|
//남은게 있다면 삭제
|
|
if (childFile.Extension == ".zip" || childFile.Extension == ".7z")
|
|
{
|
|
try
|
|
{
|
|
Debug.Log($"delete zip file:{childFile.FullName}");
|
|
childFile.Delete();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
}
|
|
}
|
|
if (childFile.Extension != ".sqlite") continue;
|
|
var sqlFileName = childFile.Name;
|
|
string time = sqlFileName.Split("_")[1].Replace(".sqlite", "");
|
|
newData[date].Add(new UIPlaybackListItemData()
|
|
{
|
|
date = date,
|
|
time = time,
|
|
zipFileName = $"{sqlFileName}.7z",
|
|
sqlFileName = sqlFileName,
|
|
status = UIPlaybackListItemStatus.Downloaded,
|
|
});
|
|
//fileName.Split(".")[0] + ".sqlite"
|
|
}
|
|
|
|
if (newData.ContainsKey(date) && newData[date].Count() > 0) dateList.Add(new TMP_Dropdown.OptionData(date));
|
|
}
|
|
}
|
|
|
|
this.data = newData;
|
|
dateList.Sort((b, a) => a.text.CompareTo(b.text));
|
|
dropdownDate.options = dateList;
|
|
dropdownDate.value = -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 서버에서 받은 날짜/시간대 데이터를 리스트에 반영합니다.
|
|
/// </summary>
|
|
/// <param name="data">서버에서 받은 데이터</param>
|
|
public void SetData(Dictionary<string, Dictionary<string, string>> data)
|
|
{
|
|
//{
|
|
// "2024-12-05": {
|
|
// "0": "2024-12-05_0.sqlite.7z",
|
|
// "1": "2024-12-05_1.sqlite.7z",
|
|
// "2": "2024-12-05_2.sqlite.7z",
|
|
// }
|
|
//}
|
|
Dictionary<string, Dictionary<string, string>> dataList = new Dictionary<string, Dictionary<string, string>>();
|
|
|
|
// 날짜/시간대 정규화
|
|
foreach (var keyPair in data)
|
|
{
|
|
if (!dataList.ContainsKey(keyPair.Key)) dataList[keyPair.Key] = new Dictionary<string, string>();
|
|
foreach (var keyPair2 in keyPair.Value)
|
|
{
|
|
var arr = keyPair2.Value.Split(".");
|
|
string timeStr = arr[0];
|
|
DateTime dateTime = DateTimeUtil.Parse(timeStr, "yyyy-MM-dd_H");
|
|
string dateStr = dateTime.ToString("yyyy-MM-dd");
|
|
string dateKey = dateTime.Hour.ToString();
|
|
string dateValue = dateTime.ToString("yyyy-MM-dd_H") + "." + arr[1] + "." + arr[2];
|
|
ULog.Debug($"dateTime:{dateTime}, timeStr:{timeStr} dateValue:{dateValue}");
|
|
if (!dataList.ContainsKey(dateStr)) dataList[dateStr] = new Dictionary<string, string>();
|
|
if (!dataList[dateStr].ContainsKey(dateKey)) dataList[dateStr][dateKey] = dateValue;
|
|
}
|
|
}
|
|
|
|
foreach (var item in dataList.ToList())
|
|
{
|
|
if (item.Key.Count() == 0) dataList.Remove(item.Key);
|
|
}
|
|
|
|
|
|
Dictionary<string, List<UIPlaybackListItemData>> newData = new Dictionary<string, List<UIPlaybackListItemData>>();
|
|
|
|
var dateList = new List<TMP_Dropdown.OptionData>();
|
|
|
|
//로컬에 저장 되 있는데 sqlite 파일 찾아서 추가
|
|
string playbackPath = PlaybackService.PlaybackFolderPath;
|
|
DirectoryInfo di = new DirectoryInfo(playbackPath);
|
|
if (di.Exists)
|
|
{
|
|
var childDi = di.EnumerateDirectories();
|
|
foreach (var child in childDi)
|
|
{
|
|
string date = child.Name;
|
|
//Debug.Log($"date:{date}");
|
|
var childFiles = child.EnumerateFiles();
|
|
if (childFiles.Count() > 0 && !newData.ContainsKey(date)) newData[date] = new List<UIPlaybackListItemData>();
|
|
foreach (var childFile in childFiles)
|
|
{
|
|
//남은게 있다면 삭제
|
|
if (childFile.Extension == ".zip" || childFile.Extension == ".7z")
|
|
{
|
|
try
|
|
{
|
|
Debug.Log($"delete zip file:{childFile.FullName}");
|
|
childFile.Delete();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
}
|
|
}
|
|
if (childFile.Extension != ".sqlite") continue;
|
|
var sqlFileName = childFile.Name;
|
|
string time = sqlFileName.Split("_")[1].Replace(".sqlite", "");
|
|
newData[date].Add(new UIPlaybackListItemData()
|
|
{
|
|
date = date,
|
|
time = time,
|
|
zipFileName = $"{sqlFileName}.7z",
|
|
sqlFileName = sqlFileName,
|
|
status = UIPlaybackListItemStatus.Downloaded,
|
|
});
|
|
//fileName.Split(".")[0] + ".sqlite"
|
|
}
|
|
|
|
if (newData.ContainsKey(date) && newData[date].Count() > 0) dateList.Add(new TMP_Dropdown.OptionData(date));
|
|
}
|
|
}
|
|
|
|
// 서버 데이터 기준으로 추가
|
|
foreach (var keyPair in dataList)
|
|
{
|
|
string date = keyPair.Key;
|
|
if (dateList.FindIndex(o => o.text == date) == -1) dateList.Add(new TMP_Dropdown.OptionData(date));
|
|
if (!newData.ContainsKey(date)) newData[date] = new List<UIPlaybackListItemData>();
|
|
foreach (var keyPair2 in keyPair.Value)
|
|
{
|
|
var zipFileName = keyPair2.Value;
|
|
var sqlFileName = zipFileName.Replace(".zip", "").Replace(".7z", "");
|
|
if (newData[date].FindIndex(item => item.sqlFileName == sqlFileName) == -1)
|
|
{
|
|
newData[date].Add(new UIPlaybackListItemData()
|
|
{
|
|
date = date,
|
|
time = keyPair2.Key,
|
|
zipFileName = zipFileName,
|
|
sqlFileName = sqlFileName,
|
|
status = UIPlaybackListItemStatus.Default,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 시간 오름차순 정렬
|
|
if (newData.ContainsKey(date)) newData[date].Sort((a, b) => int.Parse(a.time) - int.Parse(b.time));
|
|
}
|
|
|
|
this.data = newData;
|
|
dateList.Sort((b, a) => a.text.CompareTo(b.text));
|
|
dropdownDate.options = dateList;
|
|
dropdownDate.value = -1;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 날짜 드롭다운이 변경될 때 호출됩니다.
|
|
/// </summary>
|
|
/// <param name="value">선택된 인덱스</param>
|
|
private void DropDownDateChanged(int value)
|
|
{
|
|
// 선택된 날짜의 시간대 리스트를 갱신
|
|
//dropdownDatePlaceHolder.gameObject.SetActive(value == -1);
|
|
string key = dropdownDate.options[dropdownDate.value].text;
|
|
Debug.Log($"DropDownDateChanged dropdownDate.value:{dropdownDate.value} value:{value} key:{key}");
|
|
if (data != null && data.ContainsKey(key))
|
|
{
|
|
if (UIPlaybackListItem.ActiveItems.Count > 0) UIPlaybackListItem.ReleaseAll();
|
|
List<UIPlaybackListItemData> itemList = data[key];
|
|
foreach (var itemData in itemList)
|
|
{
|
|
UIPlaybackListItem item = UIPlaybackListItem.CreateFromPool(scrollRectTime.content);
|
|
item.Init(itemData);
|
|
item.OnSelect += OnItemSelect;
|
|
item.OnChangeStatus += OnItemChangeStatus;
|
|
}
|
|
selectedItem = null;
|
|
updateButtonStatus();
|
|
}
|
|
|
|
// 스크롤을 맨 위로 이동
|
|
scrollRectTime.normalizedPosition = new Vector2(0, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 시간대 아이템이 선택될 때 호출됩니다.
|
|
/// </summary>
|
|
/// <param name="data">선택된 아이템 데이터</param>
|
|
/// <param name="selected">선택 여부</param>
|
|
private void OnItemSelect(UIPlaybackListItemData data, bool selected)
|
|
{
|
|
if (selected)
|
|
{
|
|
List<UIPlaybackListItem> itemList = UIPlaybackListItem.ActiveItems;
|
|
foreach (var item in itemList)
|
|
{
|
|
if (item.Data.sqlFileName != data.sqlFileName)
|
|
{
|
|
item.Selected = false;
|
|
}
|
|
}
|
|
selectedItem = data;
|
|
}
|
|
else
|
|
{
|
|
selectedItem = null;
|
|
}
|
|
updateButtonStatus();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 다운로드 상태가 변경될 때 호출됩니다.
|
|
/// </summary>
|
|
private void OnItemChangeStatus(UIPlaybackListItemData data, UIPlaybackListItemStatus status)
|
|
{
|
|
bool enable = UIPlaybackListItem.DownloadingItems.Count == 0;
|
|
dropdownDate.interactable = enable;
|
|
}
|
|
|
|
/// <summary>
|
|
/// '확인' 버튼의 활성화 상태를 갱신합니다.
|
|
/// </summary>
|
|
private void updateButtonStatus()
|
|
{
|
|
confirmButton.interactable = IsOkable;
|
|
}
|
|
|
|
/// <summary>
|
|
/// '확인' 버튼 클릭 시 호출됩니다.
|
|
/// </summary>
|
|
public override void OnConfirmButtonClicked()
|
|
{
|
|
resultData = selectedItem;
|
|
}
|
|
|
|
}
|
|
}
|