// // EpisodeList.swift // // Copyright 2026 Brendan Szymanski // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-or-later // import Adwaita import Foundation import LuminateCore struct EpisodeList: View { var seriesId: String var seasonId: String var client: JellyfinClient var userId: String @State private var episodes: [Components.Schemas.BaseItemDto] = [] var view: Body { VStack { ForEach(episodes) { episode in EpisodeRow( episode: episode, client: client ) } } .onAppear { loadEpisodes() } } private func loadEpisodes() { Task { let result = try? await client.getEpisodes( seriesId: seriesId, userId: userId, seasonId: seasonId ) await MainActor.run { episodes = result?.items ?? [] } } } } struct EpisodeRow: View { var episode: Components.Schemas.BaseItemDto var client: JellyfinClient @State private var imageData: Data? var view: Body { HStack { if let data = imageData { Picture() .data(data) .frame(minWidth: 100, minHeight: 56) .frame(maxWidth: 100) .frame(maxHeight: 56) .card() } else { Box(spacing: 0) {} .frame(minWidth: 100, minHeight: 56) .frame(maxWidth: 100) .frame(maxHeight: 56) .card() } VStack { Text("\(episode.indexNumber ?? 0). \(episode.name ?? "")") .body() .halign(.start) Text(episode.runtimeString) .caption() .halign(.start) } .hexpand(true) Button(icon: .default(icon: .mediaPlaybackStart)) { } .flat() } .padding(10, .horizontal) .onAppear { loadImage() } } private func loadImage() { guard let tag = episode.primaryImageTag, let itemId = episode.id else { return } Task { guard let url = await client.imageURL( itemId: itemId, imageType: .primary, tag: tag, maxWidth: 200 ) else { return } let service = ImageService() imageData = try? await service.loadImage(url: url) } } }