// // CharacterDetailGalleryViewModel.swift // SodaLive // // Created by klaus on 9/2/25. // import Foundation import Combine import Moya final class CharacterDetailGalleryViewModel: ObservableObject { // MARK: - Published State @Published var isLoading: Bool = false @Published var errorMessage: String = "" @Published var isShowPurchaseDialog = false @Published var isShowImageViewer = false @Published var isShowPopup = false // Gallery Data @Published var totalCount: Int = 0 @Published var ownedCount: Int = 0 @Published var galleryItems: [CharacterImageListItemResponse] = [] @Published var selectedImageIndex: Int = 0 // MARK: - Private private let repository = CharacterDetailGalleryRepository() private var subscription = Set() private var currentPage = 0 private var isLoadingMore = false private var hasMorePages = true private var selectedItem: CharacterImageListItemResponse? var characterId: Int = 0 // MARK: - Computed Properties var ownershipPercentage: Int { guard totalCount > 0 else { return 0 } return Int(round(Double(ownedCount) / Double(totalCount) * 100)) } var ownedImages: [CharacterImageListItemResponse] { return galleryItems.filter { $0.isOwned } } var selectedItemPrice: String { return "\(selectedItem?.imagePriceCan ?? 0)캔으로 구매" } // MARK: - Public Methods func loadInitialData() { currentPage = 0 hasMorePages = true galleryItems.removeAll() loadImageList() } func loadMoreIfNeeded(currentItem: CharacterImageListItemResponse) { guard !isLoadingMore, hasMorePages, let lastItem = galleryItems.last, lastItem.id == currentItem.id else { return } loadMoreImages() } func onImageTapped(_ item: CharacterImageListItemResponse, index: Int) { selectedItem = item if item.isOwned { // 소유한 이미지 - 전체화면 뷰어 표시 selectedImageIndex = ownedImages.firstIndex { $0.id == item.id } ?? 0 isShowImageViewer = true } else { // 미소유 이미지 - 구매 다이얼로그 표시 isShowPurchaseDialog = true } } func onPurchaseConfirm() { // TODO: 구매 API 연동 구현 예정 print("구매 확인: \(selectedItem?.id ?? 0)") isShowPurchaseDialog = false } func onPurchaseCancel() { isShowPurchaseDialog = false selectedItem = nil } // MARK: - Private Methods private func loadImageList() { guard !isLoading else { return } isLoading = true repository.getCharacterImageList( characterId: characterId, page: currentPage, size: 20 ) .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [weak self] response in let responseData = response.data self?.isLoading = false do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let data = decoded.data, decoded.success { self?.handleImageListResponse(data) } else { if let message = decoded.message { self?.errorMessage = message } else { self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self?.isShowPopup = true } } catch { ERROR_LOG(String(describing: error)) self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.isShowPopup = true } } .store(in: &subscription) } private func loadMoreImages() { guard !isLoadingMore, hasMorePages else { return } isLoadingMore = true currentPage += 1 repository.getCharacterImageList( characterId: characterId, page: currentPage, size: 20 ) .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) } } receiveValue: { [weak self] response in let responseData = response.data self?.isLoading = false do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let data = decoded.data, decoded.success { self?.handleImageListResponse(data) } else { if let message = decoded.message { self?.errorMessage = message } else { self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self?.isShowPopup = true } } catch { ERROR_LOG(String(describing: error)) self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.isShowPopup = true } } .store(in: &subscription) } private func handleImageListResponse(_ response: CharacterImageListResponse) { totalCount = response.totalCount ownedCount = response.ownedCount galleryItems = response.items hasMorePages = response.items.count >= 20 } private func handleMoreImagesResponse(_ response: CharacterImageListResponse) { galleryItems.append(contentsOf: response.items) hasMorePages = response.items.count >= 20 } }