import SwiftUI import FlixyDesign import PlexKit /// Eine waagerechte Reihe von Titeln. struct MediaRow: View { let hub: PlexHub @Environment(FlixySession.self) private var session /// Breit genug, dass die übernächste Kachel angeschnitten ist — das zeigt, /// dass es weitergeht, ohne einen Pfeil dafür zu brauchen. private let landscapeWidth: CGFloat = 220 /// Schmaler, weil hochkant: bei gleicher Höhe passen so mehr Plakate /// nebeneinander, und mehr Auswahl auf einen Blick ist genau der Zweck. private let posterWidth: CGFloat = 116 var body: some View { VStack(alignment: .leading, spacing: FlixySpacing.regular) { Text(hub.title) .font(.flixyRowHeader) .foregroundStyle(Color.flixyTextPrimary) .padding(.horizontal, FlixySpacing.screenMargin) ScrollView(.horizontal) { LazyHStack(spacing: FlixySpacing.regular) { ForEach(hub.items) { item in NavigationLink(value: MediaRoute(ratingKey: item.ratingKey, title: item.title)) { card(for: item) } .buttonStyle(.plain) } } .padding(.horizontal, FlixySpacing.screenMargin) // Ohne dieses Ziel rasten die Kacheln nicht ein und die Reihe // bleibt an beliebiger Stelle stehen. .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .scrollIndicators(.hidden) } } /// Szenenbild beim Weiterschauen, sonst das Plakat. /// /// Angefangenes erkennt man an der Szene, an der man aufgehört hat — /// dafür ist das Querformat richtig. Wer dagegen aussucht, was er als /// Nächstes sehen will, sucht nach Plakaten. @ViewBuilder private func card(for item: PlexItem) -> some View { if hub.isContinueWatching { LandscapeCard( title: item.rowTitle, subtitle: item.cardSubtitle, artworkURL: session.server?.artworkURL( path: item.rowArtworkPath, width: 440, height: 248 ), progress: item.watchProgress ) .frame(width: landscapeWidth) } else { PosterCard( title: item.rowTitle, subtitle: item.cardSubtitle, artworkURL: session.server?.artworkURL( path: item.posterArtworkPath, width: 300, height: 450 ), progress: item.watchProgress ) .frame(width: posterWidth) } } } extension PlexItem { /// Der Titel auf einer Kachel in einer Reihe. /// /// Bei Folgen der Serientitel: in „Weiterschauen" stehen Folgen /// verschiedener Serien nebeneinander, und der Folgentitel allein sagt /// nicht, worum es geht. var rowTitle: String { kind == .episode ? (grandparentTitle ?? title) : title } /// Welches Bild auf die Querformat-Kachel gehört. /// /// Bei Folgen ist `thumb` das Szenenbild der Folge — genau richtig. Bei /// Filmen ist `thumb` das Hochkant-Plakat, das im Querformat beschnitten /// würde; dort ist `art` (das Hintergrundbild) die bessere Wahl. var rowArtworkPath: String? { switch kind { case .episode: thumb ?? grandparentThumb default: art ?? thumb } } /// Welches Bild auf die Hochkant-Kachel gehört. /// /// Bei Folgen das Plakat der Serie statt des Szenenbilds: ein 16:9-Bild /// hochkant beschnitten zeigt einen zufälligen Ausschnitt und ist zum /// Wiedererkennen wertlos. var posterArtworkPath: String? { switch kind { case .episode: grandparentThumb ?? parentThumb ?? thumb default: thumb } } }