feat(content): 전체 탭을 추가한다
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class MainContentAllViewModel: ObservableObject {
|
||||
private let repository = MainContentAllRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
private let pageSize = 20
|
||||
private var latestRequestId = 0
|
||||
|
||||
@Published var selectedType: MainContentAllType = .audio
|
||||
@Published var selectedSort: ContentSort = .latest
|
||||
@Published var selectedDayOfWeek: SeriesPublishedDaysOfWeek = .currentDeviceDay
|
||||
@Published var response: MainContentAllTabResponse?
|
||||
@Published var audios = [MainContentAudioResponse]()
|
||||
@Published var series = [MainContentSeriesResponse]()
|
||||
@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 allowedSorts: [ContentSort] {
|
||||
selectedType == .free ? [.latest, .popular] : ContentSort.allCases
|
||||
}
|
||||
|
||||
var shouldShowEmptyState: Bool {
|
||||
guard hasLoaded, isLoading == false else { return false }
|
||||
return selectedType.usesAudioItems ? audios.isEmpty : series.isEmpty
|
||||
}
|
||||
|
||||
func fetchFirstPageIfNeeded() {
|
||||
guard hasLoaded == false else { return }
|
||||
fetchFirstPage()
|
||||
}
|
||||
|
||||
func fetchFirstPage() {
|
||||
fetchContents(page: 0, isNextPage: false)
|
||||
}
|
||||
|
||||
func selectType(_ type: MainContentAllType) {
|
||||
guard selectedType != type else { return }
|
||||
|
||||
selectedType = type
|
||||
if allowedSorts.contains(selectedSort) == false {
|
||||
selectedSort = .latest
|
||||
}
|
||||
clearItems()
|
||||
fetchFirstPage()
|
||||
}
|
||||
|
||||
func selectSort(_ sort: ContentSort) {
|
||||
guard allowedSorts.contains(sort), selectedSort != sort else { return }
|
||||
|
||||
selectedSort = sort
|
||||
clearItems()
|
||||
fetchFirstPage()
|
||||
}
|
||||
|
||||
func selectDayOfWeek(_ dayOfWeek: SeriesPublishedDaysOfWeek) {
|
||||
guard selectedType == .series, selectedDayOfWeek != dayOfWeek else { return }
|
||||
|
||||
selectedDayOfWeek = dayOfWeek
|
||||
clearItems()
|
||||
fetchFirstPage()
|
||||
}
|
||||
|
||||
func fetchNextPageIfNeeded(currentAudio: MainContentAudioResponse) {
|
||||
guard audios.last?.audioContentId == currentAudio.audioContentId else { return }
|
||||
fetchNextPageIfPossible()
|
||||
}
|
||||
|
||||
func fetchNextPageIfNeeded(currentSeries: MainContentSeriesResponse) {
|
||||
guard series.last?.seriesId == currentSeries.seriesId 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: selectedType,
|
||||
sort: selectedSort,
|
||||
page: requestPage,
|
||||
size: pageSize,
|
||||
dayOfWeek: selectedDayOfWeek
|
||||
)
|
||||
.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<MainContentAllTabResponse>.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: MainContentAllTabResponse, isNextPage: Bool) {
|
||||
response = data
|
||||
selectedType = data.type
|
||||
selectedSort = data.sort
|
||||
if let dayOfWeek = data.dayOfWeek {
|
||||
selectedDayOfWeek = dayOfWeek
|
||||
}
|
||||
page = data.page
|
||||
size = data.size
|
||||
hasNext = data.hasNext
|
||||
|
||||
if data.type.usesAudioItems {
|
||||
if isNextPage {
|
||||
audios.append(contentsOf: data.audios)
|
||||
} else {
|
||||
audios = data.audios
|
||||
series = []
|
||||
}
|
||||
} else {
|
||||
if isNextPage {
|
||||
series.append(contentsOf: data.series)
|
||||
} else {
|
||||
series = data.series
|
||||
audios = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func applyFailureState() {
|
||||
errorMessage = I18n.MainContentAll.loadFailedMessage
|
||||
isShowPopup = true
|
||||
hasLoaded = true
|
||||
isLoading = false
|
||||
isLoadingNextPage = false
|
||||
}
|
||||
|
||||
private func clearItems() {
|
||||
audios = []
|
||||
series = []
|
||||
response = nil
|
||||
page = 0
|
||||
hasNext = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension SeriesPublishedDaysOfWeek {
|
||||
static var currentDeviceDay: SeriesPublishedDaysOfWeek {
|
||||
switch Calendar.current.component(.weekday, from: Date()) {
|
||||
case 1:
|
||||
return .SUN
|
||||
case 2:
|
||||
return .MON
|
||||
case 3:
|
||||
return .TUE
|
||||
case 4:
|
||||
return .WED
|
||||
case 5:
|
||||
return .THU
|
||||
case 6:
|
||||
return .FRI
|
||||
case 7:
|
||||
return .SAT
|
||||
default:
|
||||
return .SUN
|
||||
}
|
||||
}
|
||||
|
||||
static var allFilterCases: [SeriesPublishedDaysOfWeek] {
|
||||
[.MON, .TUE, .WED, .THU, .FRI, .SAT, .SUN, .RANDOM]
|
||||
}
|
||||
|
||||
var shortTitle: String {
|
||||
switch self {
|
||||
case .SUN:
|
||||
return I18n.MainContentAll.DayOfWeek.sun
|
||||
case .MON:
|
||||
return I18n.MainContentAll.DayOfWeek.mon
|
||||
case .TUE:
|
||||
return I18n.MainContentAll.DayOfWeek.tue
|
||||
case .WED:
|
||||
return I18n.MainContentAll.DayOfWeek.wed
|
||||
case .THU:
|
||||
return I18n.MainContentAll.DayOfWeek.thu
|
||||
case .FRI:
|
||||
return I18n.MainContentAll.DayOfWeek.fri
|
||||
case .SAT:
|
||||
return I18n.MainContentAll.DayOfWeek.sat
|
||||
case .RANDOM:
|
||||
return I18n.MainContentAll.DayOfWeek.random
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user