jellyflood/Shared/ViewModels/EPGViewModel.swift

139 lines
4.1 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
import SwiftUI
final class EPGViewModel: ViewModel, Stateful {
enum Action: Equatable {
case refresh
}
enum BackgroundState: Hashable {
case refresh
}
enum State: Hashable {
case initial
case refreshing
case content
case error(JellyfinAPIError)
}
@Published
var channelPrograms: [ChannelProgram] = []
@Published
var state: State = .initial
@Published
var backgroundStates: Set<BackgroundState> = []
// Time window configuration
var timeWindowStart: Date {
Calendar.current.date(byAdding: .hour, value: -1, to: .now) ?? .now
}
var timeWindowEnd: Date {
Calendar.current.date(byAdding: .hour, value: 12, to: .now) ?? .now
}
// Auto-refresh timer
private var refreshTimer: Timer?
private let refreshInterval: TimeInterval = 300 // 5 minutes
override init() {
super.init()
setupRefreshTimer()
}
deinit {
refreshTimer?.invalidate()
}
func respond(to action: Action) -> State {
switch action {
case .refresh:
Task {
await fetchChannelsAndPrograms()
}
return .refreshing
}
}
func fetchChannelsAndPrograms() async {
do {
state = .refreshing
// Fetch all channels
var channelParameters = Paths.GetLiveTvChannelsParameters()
channelParameters.fields = .MinimumFields
channelParameters.userID = userSession.user.id
channelParameters.sortBy = [ItemSortBy.name]
// No limit - fetch all channels for EPG
let channelRequest = Paths.getLiveTvChannels(parameters: channelParameters)
let channelResponse = try await userSession.client.send(channelRequest)
guard let channels = channelResponse.value.items, !channels.isEmpty else {
state = .content
return
}
// Fetch programs for all channels in time window
var programParameters = Paths.GetLiveTvProgramsParameters()
programParameters.channelIDs = channels.compactMap(\.id)
programParameters.userID = userSession.user.id
programParameters.minEndDate = timeWindowStart
programParameters.maxStartDate = timeWindowEnd
programParameters.sortBy = [ItemSortBy.startDate]
programParameters.fields = .MinimumFields
let programRequest = Paths.getLiveTvPrograms(parameters: programParameters)
let programResponse = try await userSession.client.send(programRequest)
// Group programs by channel
let groupedPrograms = (programResponse.value.items ?? [])
.grouped { program in
channels.first(where: { $0.id == program.channelID })
}
// Create ChannelProgram objects
let programs: [ChannelProgram] = channels
.reduce(into: [:]) { partialResult, channel in
partialResult[channel] = (groupedPrograms[channel] ?? [])
.sorted(using: \.startDate)
}
.map(ChannelProgram.init)
.sorted(using: \.channel.name)
await MainActor.run {
self.channelPrograms = programs
self.state = .content
}
} catch {
await MainActor.run {
self.state = .error(JellyfinAPIError(error.localizedDescription))
}
}
}
private func setupRefreshTimer() {
refreshTimer = Timer.scheduledTimer(withTimeInterval: refreshInterval, repeats: true) { [weak self] _ in
guard let self = self else { return }
Task {
await self.fetchChannelsAndPrograms()
}
}
}
}