174 lines
4.9 KiB
Swift
174 lines
4.9 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import Combine
|
|
import Foundation
|
|
import JellyfinAPI
|
|
|
|
final class ProgramsViewModel: ViewModel, Stateful {
|
|
|
|
enum ProgramSection: CaseIterable {
|
|
case kids
|
|
case movies
|
|
case news
|
|
case recommended
|
|
case series
|
|
case sports
|
|
}
|
|
|
|
// MARK: Action
|
|
|
|
enum Action: Equatable {
|
|
case error(JellyfinAPIError)
|
|
case refresh
|
|
}
|
|
|
|
// MARK: State
|
|
|
|
enum State: Hashable {
|
|
case content
|
|
case error(JellyfinAPIError)
|
|
case initial
|
|
case refreshing
|
|
}
|
|
|
|
@Published
|
|
private(set) var kids: [BaseItemDto] = []
|
|
@Published
|
|
private(set) var movies: [BaseItemDto] = []
|
|
@Published
|
|
private(set) var news: [BaseItemDto] = []
|
|
@Published
|
|
private(set) var recommended: [BaseItemDto] = []
|
|
@Published
|
|
private(set) var series: [BaseItemDto] = []
|
|
@Published
|
|
private(set) var sports: [BaseItemDto] = []
|
|
|
|
@Published
|
|
var state: State = .initial
|
|
|
|
private var currentRefreshTask: AnyCancellable?
|
|
|
|
var hasNoResults: Bool {
|
|
[
|
|
kids,
|
|
movies,
|
|
news,
|
|
recommended,
|
|
series,
|
|
sports,
|
|
].allSatisfy(\.isEmpty)
|
|
}
|
|
|
|
func respond(to action: Action) -> State {
|
|
switch action {
|
|
case let .error(error):
|
|
return .error(error)
|
|
case .refresh:
|
|
currentRefreshTask?.cancel()
|
|
|
|
currentRefreshTask = Task { [weak self] in
|
|
guard let self else { return }
|
|
|
|
do {
|
|
let sections = try await getItemSections()
|
|
|
|
guard !Task.isCancelled else { return }
|
|
|
|
await MainActor.run {
|
|
self.kids = sections[.kids] ?? []
|
|
self.movies = sections[.movies] ?? []
|
|
self.news = sections[.news] ?? []
|
|
self.recommended = sections[.recommended] ?? []
|
|
self.series = sections[.series] ?? []
|
|
self.sports = sections[.sports] ?? []
|
|
|
|
self.state = .content
|
|
}
|
|
} catch {
|
|
guard !Task.isCancelled else { return }
|
|
|
|
await MainActor.run {
|
|
self.send(.error(.init(error.localizedDescription)))
|
|
}
|
|
}
|
|
}
|
|
.asAnyCancellable()
|
|
|
|
return .refreshing
|
|
}
|
|
}
|
|
|
|
private func getItemSections() async throws -> [ProgramSection: [BaseItemDto]] {
|
|
try await withThrowingTaskGroup(
|
|
of: (ProgramSection, [BaseItemDto]).self,
|
|
returning: [ProgramSection: [BaseItemDto]].self
|
|
) { group in
|
|
|
|
// sections
|
|
for section in ProgramSection.allCases {
|
|
group.addTask {
|
|
let items = try await self.getPrograms(for: section)
|
|
return (section, items)
|
|
}
|
|
}
|
|
|
|
// recommended
|
|
group.addTask {
|
|
let items = try await self.getRecommendedPrograms()
|
|
return (ProgramSection.recommended, items)
|
|
}
|
|
|
|
var programs: [ProgramSection: [BaseItemDto]] = [:]
|
|
|
|
while let items = try await group.next() {
|
|
programs[items.0] = items.1
|
|
}
|
|
|
|
return programs
|
|
}
|
|
}
|
|
|
|
private func getRecommendedPrograms() async throws -> [BaseItemDto] {
|
|
|
|
var parameters = Paths.GetRecommendedProgramsParameters()
|
|
parameters.fields = .MinimumFields
|
|
.appending(.channelInfo)
|
|
parameters.isAiring = true
|
|
parameters.limit = 20
|
|
parameters.userID = userSession.user.id
|
|
|
|
let request = Paths.getRecommendedPrograms(parameters: parameters)
|
|
let response = try await userSession.client.send(request)
|
|
|
|
return response.value.items ?? []
|
|
}
|
|
|
|
private func getPrograms(for section: ProgramSection) async throws -> [BaseItemDto] {
|
|
|
|
var parameters = Paths.GetLiveTvProgramsParameters()
|
|
parameters.fields = .MinimumFields
|
|
.appending(.channelInfo)
|
|
parameters.hasAired = false
|
|
parameters.limit = 20
|
|
parameters.userID = userSession.user.id
|
|
|
|
parameters.isKids = section == .kids
|
|
parameters.isMovie = section == .movies
|
|
parameters.isNews = section == .news
|
|
parameters.isSeries = section == .series
|
|
parameters.isSports = section == .sports
|
|
|
|
let request = Paths.getLiveTvPrograms(parameters: parameters)
|
|
let response = try await userSession.client.send(request)
|
|
|
|
return response.value.items ?? []
|
|
}
|
|
}
|