ProjectDDD/Assets/_DDD/_Scripts/Restaurant/Ui/RestaurantManagementUi/RestaurantManagementViewModel.cs
2025-08-27 17:25:11 +09:00

608 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Localization;
namespace DDD.Restaurant
{
public class RestaurantManagementViewModel : SimpleViewModel, IEventHandler<TodayMenuRemovedEvent>
{
// View에서 구독할 이벤트들
public Action OnBatchCompleted;
public Action OnChecklistFailed;
public Action OnMenuSectionSelected;
public Action OnCookwareSectionSelected;
public Action<InventoryCategoryType> OnCategoryChanged;
public Action<int> OnTabMoved;
public Action OnInteractRequested;
public Action OnCloseRequested;
public Action<InventoryCategoryType> OnMenuCategorySelected;
private RestaurantManagementData GetRestaurantManagementData() => RestaurantData.Instance.ManagementData;
private RestaurantManagementState GetRestaurantManagementState() => RestaurantState.Instance.ManagementState;
public override void Initialize()
{
base.Initialize();
RegisterEvents();
ResetHoldState();
}
public override void Cleanup()
{
base.Cleanup();
UnregisterEvents();
}
private void RegisterEvents()
{
EventBus.Register<TodayMenuRemovedEvent>(this);
}
private void UnregisterEvents()
{
EventBus.Unregister<TodayMenuRemovedEvent>(this);
}
public void InteractWithSelected()
{
OnInteractRequested?.Invoke();
}
public void CloseUi()
{
OnCloseRequested?.Invoke();
}
public void HandleEvent(TodayMenuRemovedEvent evt)
{
SetCategory(evt.InventoryCategoryType);
OnMenuCategorySelected?.Invoke(evt.InventoryCategoryType);
}
#region RestaurantManagementView
private bool _isHolding;
private float _holdProgress;
public float HoldProgress
{
get => _holdProgress;
private set
{
if (SetField(ref _holdProgress, value))
{
OnPropertyChanged(nameof(NormalizedHoldProgress));
}
}
}
public float NormalizedHoldProgress => GetRestaurantManagementData().HoldCompleteTime <= 0f ? 0f : Mathf.Clamp01(_holdProgress / GetRestaurantManagementData().HoldCompleteTime);
public bool CanCompleteBatch => GetRestaurantManagementState().GetChecklistStates().All(state => state);
public void UpdateHoldProgress()
{
if (_isHolding == false) return;
if (GetRestaurantManagementData().HoldCompleteTime <= 0f)
{
ProcessCompleteBatch();
return;
}
var deltaTime = Time.deltaTime;
HoldProgress += deltaTime;
if (HoldProgress >= GetRestaurantManagementData().HoldCompleteTime)
{
ProcessCompleteBatch();
}
}
public void StartHold()
{
_isHolding = true;
HoldProgress = 0f;
}
public void CancelHold()
{
ResetHoldState();
}
private void ResetHoldState()
{
_isHolding = false;
HoldProgress = 0f;
}
private void ProcessCompleteBatch()
{
ResetHoldState();
if (CanCompleteBatch)
{
// 배치 완료 - UI 닫기 이벤트 발생
OnBatchCompleted?.Invoke();
}
else
{
// 체크리스트 미완료 - 실패 팝업 표시 이벤트 발생
OnChecklistFailed?.Invoke();
}
}
#endregion
#region TabGroup
// 탭 상태 관리
private SectionButtonType _currentSection = SectionButtonType.Menu;
public SectionButtonType CurrentSection
{
get => _currentSection;
set => SetField(ref _currentSection, value);
}
private InventoryCategoryType _currentCategory = InventoryCategoryType.Food;
public InventoryCategoryType CurrentCategory
{
get => _currentCategory;
set => SetField(ref _currentCategory, value);
}
public void SetSection(SectionButtonType section)
{
CurrentSection = section;
// 섹션 변경 시 해당 섹션의 첫 번째 카테고리로 설정
switch (section)
{
case SectionButtonType.Menu:
OnMenuSectionSelected?.Invoke();
break;
case SectionButtonType.Cookware:
OnCookwareSectionSelected?.Invoke();
break;
}
}
public void SetCategory(InventoryCategoryType category)
{
if (CurrentCategory == category) return;
BeginUpdate();
CurrentCategory = category;
RecomputeItemDetail();
EndUpdate();
OnCategoryChanged?.Invoke(category);
}
public void MoveTab(int direction)
{
OnTabMoved?.Invoke(direction);
}
#endregion
#region ChecklistView
private List<ChecklistData> _checklistDatas;
private Dictionary<ChecklistLocalizationKey, string> _checklistLocalizationKeys = new()
{
{ChecklistLocalizationKey.Checklist1, "checklist_1"},
{ChecklistLocalizationKey.Checklist2, "checklist_2"},
{ChecklistLocalizationKey.Checklist3, "checklist_3"},
};
public void CreateChecklist(Transform parent)
{
var checklistCount = GetRestaurantManagementData().ChecklistCount;
_checklistDatas = new List<ChecklistData>(checklistCount);
for (int i = 0; i < checklistCount; i++)
{
var instance = Instantiate(GetRestaurantManagementData().ChecklistDataPrefab, parent);
instance.Initialize();
_checklistDatas.Add(instance);
}
}
public void UpdateChecklistView()
{
bool[] states = GetRestaurantManagementState().GetChecklistStates();
int loopCount = Mathf.Min(_checklistDatas.Count, states.Length);
for (int i = 0; i < loopCount; i++)
{
_checklistDatas[i].UpdateData(_checklistLocalizationKeys[(ChecklistLocalizationKey)i], states[i]);
}
}
#endregion
#region InventoryView
private InventoryCategoryType _currenInventoryCategoryType = InventoryCategoryType.Food;
private readonly Dictionary<string, ItemSlotUi> _slotLookup = new();
private InventorySortType _currentSortType = InventorySortType.None;
private GameObject _firstSlot;
private const string ItemSlotUiName = "ItemSlotUi_";
public GameObject GetInitialSelectedByInventory() => _firstSlot;
public void CreateInventoryItemSlot(Transform parent)
{
Utils.DestroyAllChildren(parent);
var models = ItemViewModelFactory.CreateRestaurantManagementInventoryItem();
_slotLookup.Clear();
foreach (var model in models)
{
var itemSlotUi = Instantiate(GetRestaurantManagementData().ItemSlotUiPrefab, parent);
var slot = itemSlotUi.GetComponent<ItemSlotUi>();
slot.Initialize(model, new InventorySlotUiStrategy());
itemSlotUi.name = ItemSlotUiName + model.Id;
var interactor = itemSlotUi.GetComponent<ItemSlotInteractor>();
if (model.ItemType == ItemType.Recipe)
{
interactor.Initialize(TodayMenuEventType.Add, new TodayMenuInteractorStrategy());
}
else
{
if (DataManager.Instance.GetDataSo<CookwareDataSo>().TryGetDataById(model.Id, out var cookwareData))
{
interactor.Initialize(TodayMenuEventType.Add, new TodayCookwareInteractorStrategy());
}
}
_slotLookup[model.Id] = slot;
}
UpdateCategoryView();
}
public void SetSortType(InventorySortType sortType)
{
_currentSortType = sortType;
UpdateCategoryView();
}
private IEnumerable<ItemSlotUi> SortSlots(IEnumerable<ItemSlotUi> slots)
{
return _currentSortType switch
{
InventorySortType.NameAscending => slots.OrderByDescending(slot => slot.Model.HasItem).ThenBy(slot => slot.Model.DisplayName),
InventorySortType.NameDescending => slots.OrderByDescending(slot => slot.Model.HasItem).ThenByDescending(slot => slot.Model.DisplayName),
InventorySortType.QuantityAscending => slots.OrderByDescending(slot => slot.Model.HasItem).ThenBy(slot => slot.Model.Count),
InventorySortType.QuantityDescending => slots.OrderByDescending(slot => slot.Model.HasItem).ThenByDescending(slot => slot.Model.Count),
InventorySortType.None => slots.OrderBy(slot => slot.Model.Id),
_ => slots
};
}
public void UpdateCategoryView()
{
_currenInventoryCategoryType = _currenInventoryCategoryType == InventoryCategoryType.None ? InventoryCategoryType.Food : _currenInventoryCategoryType;
UpdateCategoryViewByCategory(_currenInventoryCategoryType);
}
public void UpdateCategoryViewByCategory(InventoryCategoryType category)
{
_currenInventoryCategoryType = category;
GameObject firstValidSlot = null;
var filteredSlots = _slotLookup.Values;
var sortedSlots = SortSlots(filteredSlots);
int siblingIndex = 0;
foreach (var slot in sortedSlots)
{
var model = slot.Model;
string id = model.Id;
bool isRegisteredTodayMenu = model.ItemType == ItemType.Recipe && GetRestaurantManagementState().IsContainTodayMenu(id);
bool matchCategory = MatchesCategory(model, _currenInventoryCategoryType);
bool shouldShow = !isRegisteredTodayMenu && matchCategory;
slot.SetActive(shouldShow);
if (shouldShow)
{
if (model.HasItem)
{
slot.transform.SetSiblingIndex(siblingIndex++);
}
if (firstValidSlot == null)
{
firstValidSlot = slot.gameObject;
}
}
}
_firstSlot = firstValidSlot;
}
private bool MatchesCategory(ItemModel model, InventoryCategoryType category)
{
switch (category)
{
case InventoryCategoryType.Food:
if (model.ItemType != ItemType.Recipe) return false;
return DataManager.Instance.GetDataSo<RecipeDataSo>()
.TryGetDataById(model.Id, out var foodRecipe) && foodRecipe.RecipeType == RecipeType.FoodRecipe;
case InventoryCategoryType.Drink:
if (model.ItemType != ItemType.Recipe) return false;
return DataManager.Instance.GetDataSo<RecipeDataSo>()
.TryGetDataById(model.Id, out var drinkRecipe) &&
drinkRecipe.RecipeType == RecipeType.DrinkRecipe;
case InventoryCategoryType.Ingredient:
return model.ItemType == ItemType.Ingredient;
case InventoryCategoryType.Cookware:
return DataManager.Instance.GetDataSo<CookwareDataSo>()
.TryGetDataById(model.Id, out var cookwareData);
case InventoryCategoryType.Special:
return false;
default:
return false;
}
}
public void OnInventoryChanged()
{
foreach (var slot in _slotLookup.Values)
{
if (slot.Strategy is InventorySlotUiStrategy inventorySlotUiStrategy)
{
inventorySlotUiStrategy.OnInventoryChanged(slot);
}
}
}
#endregion
#region ItemDetailView
private ItemDetailSnapshot _itemDetail = ItemDetailSnapshot.Empty;
public ItemDetailSnapshot ItemDetail
{
get => _itemDetail;
private set => SetField(ref _itemDetail, value);
}
public ItemModel SelectedItem { get; private set; }
private const string CookwareDetailPanel = "CookwareDetailPanel";
private const string IngredientDetailPanel = "IngredientDetailPanel";
private const string RecipeDetailPanel = "RecipeDetailPanel";
public void SetSelectedItem(ItemModel item)
{
if (SelectedItem == item) return;
BeginUpdate();
SelectedItem = item;
RecomputeItemDetail();
EndUpdate();
}
public TasteHashTagSlotUi CreateHashTag(RectTransform parent)
{
return Instantiate(GetRestaurantManagementData().TasteHashTagSlotUiPrefab, parent, false);
}
private void RecomputeItemDetail()
{
var background = CurrentCategory switch
{
InventoryCategoryType.Food or InventoryCategoryType.Drink => DataManager.Instance.GetSprite(RecipeDetailPanel),
InventoryCategoryType.Ingredient => DataManager.Instance.GetSprite(IngredientDetailPanel),
InventoryCategoryType.Cookware or InventoryCategoryType.Special => DataManager.Instance.GetSprite(CookwareDetailPanel),
_ => null
};
bool showTaste = CurrentCategory is InventoryCategoryType.Food or InventoryCategoryType.Drink or InventoryCategoryType.Ingredient;
bool showCookware = CurrentCategory is InventoryCategoryType.Food or InventoryCategoryType.Drink;
var cookwareSprite = SelectedItem.GetCookwareIcon;
string key = SelectedItem.ItemType == ItemType.Recipe ? SelectedItem.GetRecipeResultKey : SelectedItem.Id;
var nameLocalizedString = LocalizationManager.Instance.GetLocalizedName(key);
var descriptionLocalizedString = LocalizationManager.Instance.GetLocalizedDescription(key);
var tastes = SelectedItem.GetTasteDatas;
var tasteMat = SelectedItem.RecipeType switch
{
RecipeType.FoodRecipe => RestaurantData.Instance.ManagementData.FoodTasteMaterial,
RecipeType.DrinkRecipe => RestaurantData.Instance.ManagementData.DrinkTasteMaterial,
_ => null
};
ItemDetail = new ItemDetailSnapshot(
backgroundSprite: background,
showTastePanel: showTaste,
showCookwarePanel: showCookware,
cookwareSprite: cookwareSprite,
name: nameLocalizedString,
description: descriptionLocalizedString,
tastes: tastes,
tasteMaterial: tasteMat);
}
public IReadOnlyList<TasteData> GetTastes() => SelectedItem?.GetTasteDatas;
public Material GetTasteMaterial()
{
if (SelectedItem == null) return null;
var restaurantManagementData = RestaurantData.Instance.ManagementData;
return SelectedItem.RecipeType switch
{
RecipeType.FoodRecipe => restaurantManagementData.FoodTasteMaterial,
RecipeType.DrinkRecipe => restaurantManagementData.DrinkTasteMaterial,
_ => null
};
}
#endregion
#region TodayMenuView
private List<ItemSlotUi> _foodSlots;
private List<ItemSlotUi> _drinkSlots;
public void CreateFoodSlot(Transform parent)
{
Utils.DestroyAllChildren(parent);
var foodMaxCount = GetRestaurantManagementData().MaxFoodCount;
_foodSlots = new List<ItemSlotUi>(foodMaxCount);
for (int i = 0; i < foodMaxCount; i++)
{
var instance = Instantiate(GetRestaurantManagementData().ItemSlotUiPrefab, parent);
var slot = instance.GetComponent<ItemSlotUi>();
slot.Initialize(null, new TodayMenuSlotUiStrategy(RecipeType.FoodRecipe));
var itemSlotInteractor = instance.GetComponent<ItemSlotInteractor>();
itemSlotInteractor.Initialize(TodayMenuEventType.Remove, new TodayMenuInteractorStrategy());
_foodSlots.Add(slot);
}
}
public void CreateDrinkSlot(Transform parent)
{
Utils.DestroyAllChildren(parent);
var drinkMaxCount = GetRestaurantManagementData().MaxDrinkCount;
_drinkSlots = new List<ItemSlotUi>(drinkMaxCount);
for (int i = 0; i < drinkMaxCount; i++)
{
var instance = Instantiate(GetRestaurantManagementData().ItemSlotUiPrefab, parent);
var slot = instance.GetComponent<ItemSlotUi>();
slot.Initialize(null, new TodayMenuSlotUiStrategy(RecipeType.FoodRecipe));
var itemSlotInteractor = instance.GetComponent<ItemSlotInteractor>();
itemSlotInteractor.Initialize(TodayMenuEventType.Remove, new TodayMenuInteractorStrategy());
_drinkSlots.Add(slot);
}
}
public void UpdateTodayMenuItems()
{
int foodIndex = 0;
foreach (var foodRecipeIdCountPair in GetRestaurantManagementState().TodayFoodRecipeIds)
{
if (foodIndex >= _foodSlots.Count) break;
var model = ItemViewModelFactory.CreateByItemId(foodRecipeIdCountPair.Key);
var foodSlot = _foodSlots[foodIndex];
foodSlot.Initialize(model, new TodayMenuSlotUiStrategy(RecipeType.FoodRecipe));
foodSlot.Model.SetCount(foodRecipeIdCountPair.Value);
foodIndex++;
}
for (int i = foodIndex; i < _foodSlots.Count; i++)
{
_foodSlots[i].Initialize(null, new TodayMenuSlotUiStrategy(RecipeType.FoodRecipe));
}
int drinkIndex = 0;
foreach (var drinkRecipeIdCountPair in GetRestaurantManagementState().TodayDrinkRecipeIds)
{
if (drinkIndex >= _drinkSlots.Count) break;
var model = ItemViewModelFactory.CreateByItemId(drinkRecipeIdCountPair.Key);
var drinkSlot = _drinkSlots[drinkIndex];
drinkSlot.Initialize(model, new TodayMenuSlotUiStrategy(RecipeType.DrinkRecipe));
drinkSlot.Model.SetCount(drinkRecipeIdCountPair.Value);
drinkIndex++;
}
for (int i = drinkIndex; i < _drinkSlots.Count; i++)
{
_drinkSlots[i].Initialize(null, new TodayMenuSlotUiStrategy(RecipeType.DrinkRecipe));
}
}
#endregion
#region TodayRestaurantStateView
private List<ItemSlotUi> _workerSlots;
private List<ItemSlotUi> _cookwareSlots;
public void CreateTodayWorkerSlot(Transform parent)
{
Utils.DestroyAllChildren(parent);
int maxWorkerCount = GetRestaurantManagementData().MaxWorkerCount;
_workerSlots = new List<ItemSlotUi>(maxWorkerCount);
for (int i = 0; i < maxWorkerCount; i++)
{
var instance = Instantiate(GetRestaurantManagementData().ItemSlotUiPrefab, parent);
var slot = instance.GetComponent<ItemSlotUi>();
slot.Initialize(null, new TodayWorkerSlotUiStrategy());
var itemSlotInteractor = instance.GetComponent<ItemSlotInteractor>();
itemSlotInteractor.Initialize(TodayMenuEventType.Remove, new TodayCookwareInteractorStrategy());
_workerSlots.Add(slot);
}
}
public void CreateTodayCookwareSlot(Transform parent)
{
Utils.DestroyAllChildren(parent);
int maxCookwareCount = GetRestaurantManagementData().MaxCookwareCount;
_cookwareSlots = new List<ItemSlotUi>(maxCookwareCount);
for (int i = 0; i < maxCookwareCount; i++)
{
var instance = Instantiate(GetRestaurantManagementData().ItemSlotUiPrefab, parent);
var slot = instance.GetComponent<ItemSlotUi>();
slot.Initialize(null, new TodayWorkerSlotUiStrategy());
var itemSlotInteractor = instance.GetComponent<ItemSlotInteractor>();
itemSlotInteractor.Initialize(TodayMenuEventType.Remove, new TodayCookwareInteractorStrategy());
_cookwareSlots.Add(slot);
}
}
public void UpdateTodayRestaurantStateView()
{
int workerIndex = 0;
foreach (var workerKey in GetRestaurantManagementState().TodayWorkerIds)
{
if (workerIndex >= _workerSlots.Count) break;
var model = ItemViewModelFactory.CreateByItemId(workerKey);
var newWorkerSlot = _workerSlots[workerIndex];
newWorkerSlot.Initialize(model, new TodayWorkerSlotUiStrategy());
newWorkerSlot.Model.SetCount(1);
workerIndex++;
}
for (int i = workerIndex; i < _workerSlots.Count; i++)
{
_workerSlots[i].Initialize(null, new TodayWorkerSlotUiStrategy());
}
int cookwareIndex = 0;
foreach (var cookwareKey in GetRestaurantManagementState().CookwareToRecipeIds.Keys)
{
if (cookwareIndex >= _cookwareSlots.Count) break;
var model = ItemViewModelFactory.CreateByItemId(cookwareKey);
var newCookwareSlot = _cookwareSlots[cookwareIndex];
newCookwareSlot.Initialize(model, new TodayCookwareSlotUiStrategy());
newCookwareSlot.Model.SetCount(1);
cookwareIndex++;
}
for (int i = cookwareIndex; i < _cookwareSlots.Count; i++)
{
_cookwareSlots[i].Initialize(null, new TodayCookwareSlotUiStrategy());
}
}
#endregion
}
}