// // NewCharacterListViewModel.swift // SodaLive // // Created by klaus on 9/12/25. // import Foundation import Combine import Moya final class NewCharacterListViewModel: ObservableObject { // MARK: - Outputs @Published private(set) var totalCount: Int = 0 @Published private(set) var items: [Character] = [] @Published var isLoading: Bool = false @Published var isLoadingMore: Bool = false @Published var errorMessage: String = "" @Published var isShowPopup: Bool = false // MARK: - Private private let repository = NewCharacterRepository() private var subscription = Set() private var currentPage: Int = 0 private let pageSize: Int = 20 private var hasMorePages: Bool = true // MARK: - API func fetch() { // 초기 로드 currentPage = 0 hasMorePages = true items.removeAll() request(page: currentPage) } func loadMoreIfNeeded(currentIndex: Int) { guard hasMorePages, !isLoading, !isLoadingMore, currentIndex >= items.count - 1 else { return } loadMore() } // MARK: - Private private func loadMore() { guard hasMorePages, !isLoadingMore else { return } isLoadingMore = true currentPage += 1 request(page: currentPage, isLoadMore: true) } private func request(page: Int, isLoadMore: Bool = false) { if !isLoadMore { isLoading = true } repository.getRecentCharacters(page: page, size: pageSize) .receive(on: DispatchQueue.main) .sink { [weak self] completion in switch completion { case .finished: DEBUG_LOG("finish") case .failure(let error): ERROR_LOG(error.localizedDescription) if isLoadMore { self?.isLoadingMore = false } else { self?.isLoading = false } self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.isShowPopup = true } } receiveValue: { [weak self] response in guard let self = self else { return } do { let jsonDecoder = JSONDecoder() let decoded = try jsonDecoder.decode(ApiResponse.self, from: response.data) if let data = decoded.data, decoded.success { self.totalCount = data.totalCount if isLoadMore { self.items.append(contentsOf: data.content) self.isLoadingMore = false } else { self.items = data.content self.isLoading = false } // hasMore 계산 (총 개수 대비 현재 로드 수) if self.items.count >= self.totalCount || data.content.isEmpty { self.hasMorePages = false } } else { if let message = decoded.message { self.errorMessage = message } else { self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." } self.isShowPopup = true if isLoadMore { self.isLoadingMore = false } else { self.isLoading = false } } } catch { if isLoadMore { self.isLoadingMore = false } else { self.isLoading = false } self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.isShowPopup = true } } .store(in: &subscription) } }