248 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  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<AnyCancellable>()
 | 
						|
    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 {
 | 
						|
            // 미소유 이미지 - 구매 다이얼로그 표시
 | 
						|
            selectedImageIndex = index
 | 
						|
            isShowPurchaseDialog = true
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    func onPurchaseConfirm() {
 | 
						|
        isShowPurchaseDialog = false
 | 
						|
        if isLoading {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        if let imageItem = selectedItem {
 | 
						|
            isLoading = true
 | 
						|
            repository.purchaseCharacterImage(imageId: imageItem.id)
 | 
						|
                .sink { result in
 | 
						|
                    switch result {
 | 
						|
                    case .finished:
 | 
						|
                        DEBUG_LOG("finish")
 | 
						|
                    case .failure(let error):
 | 
						|
                        ERROR_LOG(error.localizedDescription)
 | 
						|
                    }
 | 
						|
                } receiveValue: { [unowned self] response in
 | 
						|
                    let responseData = response.data
 | 
						|
                    self.isLoading = false
 | 
						|
                    
 | 
						|
                    do {
 | 
						|
                        let jsonDecoder = JSONDecoder()
 | 
						|
                        let decoded = try jsonDecoder.decode(ApiResponse<CharacterImagePurchaseResponse>.self, from: responseData)
 | 
						|
                        
 | 
						|
                        if let data = decoded.data, decoded.success {
 | 
						|
                            var copyGalleryItems = self.galleryItems
 | 
						|
                            copyGalleryItems[self.selectedImageIndex].imageUrl = data.imageUrl
 | 
						|
                            copyGalleryItems[self.selectedImageIndex].isOwned = true
 | 
						|
                            self.galleryItems = copyGalleryItems
 | 
						|
                            self.ownedCount += 1
 | 
						|
                        } else {
 | 
						|
                            if let message = decoded.message {
 | 
						|
                                self.errorMessage = message
 | 
						|
                            } else {
 | 
						|
                                self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                            }
 | 
						|
                            
 | 
						|
                            self.isShowPopup = true
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        self.selectedImageIndex = 0
 | 
						|
                        self.selectedItem = nil
 | 
						|
                    } catch {
 | 
						|
                        self.selectedImageIndex = 0
 | 
						|
                        self.selectedItem = nil
 | 
						|
                        ERROR_LOG(String(describing: error))
 | 
						|
                        self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
						|
                        self.isShowPopup = true
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                .store(in: &subscription)
 | 
						|
        } else {
 | 
						|
            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<CharacterImageListResponse>.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<CharacterImageListResponse>.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
 | 
						|
    }
 | 
						|
}
 |