jellyflood/jellypig tvOS/Views/ProgramGuideView.swift

143 lines
4.0 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 SwiftUI
struct ProgramGuideView: View {
@StateObject
private var viewModel = EPGViewModel()
// Configuration
private let pixelsPerMinute: CGFloat = 3.0
@State
private var currentTime = Date.now
private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Color(red: 0.15, green: 0.05, blue: 0.1)
.ignoresSafeArea()
content
}
.onAppear {
viewModel.send(.refresh)
}
.onReceive(timer) { _ in
currentTime = Date.now
}
}
@ViewBuilder
private var content: some View {
switch viewModel.state {
case .initial:
ProgressView()
.scaleEffect(1.5)
case .refreshing:
VStack(spacing: 20) {
ProgressView()
.scaleEffect(1.5)
Text("Loading Program Guide...")
.font(.headline)
.foregroundColor(.secondary)
}
case .content:
if viewModel.channelPrograms.isEmpty {
emptyView
} else {
epgGridView
}
case let .error(error):
VStack(spacing: 20) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 60))
.foregroundColor(.red)
Text("Error Loading Guide")
.font(.title2)
.fontWeight(.semibold)
Text(error.localizedDescription)
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 100)
Button("Retry") {
viewModel.send(.refresh)
}
.buttonStyle(.card)
}
}
}
private var epgGridView: some View {
ZStack {
VStack(spacing: 0) {
// Timeline header
EPGTimelineHeader(
timeWindowStart: viewModel.timeWindowStart,
timeWindowEnd: viewModel.timeWindowEnd,
pixelsPerMinute: pixelsPerMinute
)
Divider()
// Channel rows
ScrollView(.vertical, showsIndicators: true) {
VStack(spacing: 0) {
ForEach(viewModel.channelPrograms, id: \.id) { channelProgram in
EPGChannelRow(
channelProgram: channelProgram,
timeWindowStart: viewModel.timeWindowStart,
timeWindowEnd: viewModel.timeWindowEnd,
pixelsPerMinute: pixelsPerMinute
)
Divider()
}
}
}
}
// Current time indicator overlay
EPGCurrentTimeIndicator(
timeWindowStart: viewModel.timeWindowStart,
pixelsPerMinute: pixelsPerMinute
)
}
.navigationTitle("Program Guide")
}
private var emptyView: some View {
VStack(spacing: 20) {
Image(systemName: "tv")
.font(.system(size: 80))
.foregroundColor(.secondary)
Text("No Channels Available")
.font(.title)
.fontWeight(.semibold)
Text("Check your Live TV setup in Jellyfin")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 100)
}
}
}