feat(content): 추천 전체보기를 연결한다

This commit is contained in:
Yu Sung
2026-07-07 11:54:31 +09:00
parent b33aed94c6
commit 981c02a8e1
18 changed files with 1169 additions and 294 deletions

View File

@@ -0,0 +1,118 @@
import Foundation
import Combine
final class ContentOverviewViewModel: ObservableObject {
private let repository = ContentOverviewRepository()
private var subscription = Set<AnyCancellable>()
private let pageSize = 20
private var latestRequestId = 0
let type: ContentOverviewType
@Published var items = [ContentOverviewItemResponse]()
@Published var page = 0
@Published var size = 20
@Published var hasNext = false
@Published var hasLoaded = false
@Published var isLoading = false
@Published var isLoadingNextPage = false
@Published var errorMessage = ""
@Published var isShowPopup = false
var shouldShowEmptyState: Bool {
hasLoaded && !isLoading && items.isEmpty
}
init(type: ContentOverviewType = .newAndHotAudio) {
self.type = type
}
func fetchFirstPageIfNeeded() {
guard !hasLoaded else { return }
fetchFirstPage()
}
func fetchFirstPage() {
fetchContents(page: 0, isNextPage: false)
}
func fetchNextPageIfNeeded(currentItem: ContentOverviewItemResponse) {
guard items.last?.contentId == currentItem.contentId else { return }
fetchNextPageIfPossible()
}
private func fetchNextPageIfPossible() {
guard hasNext, isLoading == false, isLoadingNextPage == false else { return }
fetchContents(page: page + 1, isNextPage: true)
}
private func fetchContents(page requestPage: Int, isNextPage: Bool) {
latestRequestId += 1
let requestId = latestRequestId
if isNextPage {
isLoadingNextPage = true
} else {
isLoading = true
}
repository.getContents(type: type, page: requestPage, size: pageSize)
.sink { [weak self] result in
guard let self else { return }
guard requestId == self.latestRequestId else { return }
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
self.applyFailureState()
}
} receiveValue: { [weak self] response in
guard let self else { return }
guard requestId == self.latestRequestId else { return }
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<ContentOverviewPageResponse>.self, from: response.data)
if let data = decoded.data, decoded.success {
self.applySuccessState(data, isNextPage: isNextPage)
} else {
self.errorMessage = decoded.message ?? I18n.MainContentAll.loadFailedMessage
self.isShowPopup = true
}
} catch {
ERROR_LOG(error.localizedDescription)
self.errorMessage = I18n.MainContentAll.loadFailedMessage
self.isShowPopup = true
}
self.hasLoaded = true
self.isLoading = false
self.isLoadingNextPage = false
}
.store(in: &subscription)
}
private func applySuccessState(_ data: ContentOverviewPageResponse, isNextPage: Bool) {
page = data.page
size = data.size
hasNext = data.hasNext
errorMessage = ""
if isNextPage {
items.append(contentsOf: data.items)
} else {
items = data.items
}
}
private func applyFailureState() {
errorMessage = I18n.MainContentAll.loadFailedMessage
isShowPopup = true
hasLoaded = true
isLoading = false
isLoadingNextPage = false
}
}