Alle Reihen zeigten Querformat-Kacheln. Für „Weiterschauen" ist das richtig — man erkennt die Szene wieder, an der man aufgehört hat. Beim Aussuchen sucht man dagegen nach Plakaten, und die sind hochkant. Die Unterscheidung hängt an der Hub-Kennung, nicht am Titel: der kommt in der Sprache des Servers, und die Startseite sähe sonst auf Englisch anders aus als auf Deutsch, ohne dass jemand auf die Ursache käme. `ondeck` zählt mit — ältere Serverfassungen nennen es so. Bei Folgen in Hochkant-Reihen wird das Serienplakat genommen, nicht das Szenenbild: ein 16:9-Bild hochkant beschnitten zeigt einen zufälligen Ausschnitt und ist zum Wiedererkennen wertlos. Plakate sind schmaler als Szenenbilder, damit bei gleicher Höhe mehr nebeneinander passen — mehr Auswahl auf einen Blick ist der Zweck. Vier Tests, darunter einer, der festhält, dass der übersetzte Titel die Form nicht bestimmen darf. 149 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
113 lines
4.0 KiB
Swift
113 lines
4.0 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|