// // EpisodeList.swift // LuminateUI // // Created by Brendan Szymanski on 6/5/26. // @preconcurrency import Adwaita import Foundation import LuminateCore struct EpisodeList: View { var seriesId: String var seasonId: String var client: JellyfinClient var userId: String @State private nonisolated(unsafe) var episodes: [Components.Schemas.BaseItemDto] = [] var view: Body { VStack { ForEach(episodes) { episode in EpisodeRow( episode: episode, client: client ) } } .onAppear { loadEpisodes() } } private func loadEpisodes() { Task { [self] in let result = try? await client.getEpisodes( seriesId: seriesId, userId: userId, seasonId: seasonId ) episodes = result?.items ?? [] } } } struct EpisodeRow: View { var episode: Components.Schemas.BaseItemDto var client: JellyfinClient @State private nonisolated(unsafe) 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 { [self] in 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) } } }