Aufmacher, Reihen, Mediatheken, Raster. Der sichtbare Teil ist neu; der Teil darunter ist derselbe wie am Telefon, und das war die eigentliche Arbeit. **Herausgelöst statt kopiert.** `HomeFeed` und `LibraryFeed` tragen jetzt, was vorher in `HomeView` und `LibraryView` stand: laden, ordnen, den Aufmacher auslosen, den Zwischenspeicher abziehen, entdoppeln, nach einem Fehlschlag sperren. Das zu kopieren wäre der schlimmste Ausgang dieses Vorhabens gewesen — an mindestens drei dieser Stellen klebte schon je ein Fehler, der sich als „es sind nicht alle Filme drin" äusserte und sonst nichts. Einer davon, das Los des Aufmachers an der Position statt am Titel, wäre in einer Kopie ein zweites Mal zu finden gewesen. Mitgewandert sind `rowTitle`, `rowArtworkPath`, `posterArtworkPath`, `runtimeLabel` und `HeroArtwork`: reine Ableitungen, die in einer Ansichtsdatei lagen und deshalb nur das Telefon übersetzte. **Nachgeladen wird am Fokus, nicht am Erscheinen.** Die alte Regel hiess „die letzten fünf" und hing daran, dass eine Kachel sichtbar wird. Beim ersten Zeichnen erscheinen aber alle sechzig, auch die letzten fünf — das Nachladen lief sofort los statt beim Blättern. Auf einem Raster aus sechs Spalten sind fünf Einträge ausserdem nicht einmal eine Zeile. `PagingWindow` rechnet deshalb in Zeilen; sechs Tests halten es fest, darunter der Fall, der die alte Regel entlarvt. **Der Fokus setzt den Aufmacher.** Wer durch „Zuletzt hinzugefügt" wandert, sieht oben, was er gerade anschaut. Kein Wechsel im Sieben- sekundentakt wie am Telefon: vor einem Fernseher wäre ein Bild, das sich von allein austauscht, während man es liest, eine Zumutung. **Drei Dinge, die erst der Simulator zeigte.** Die Seiten müssen bildfüllend laufen, weil ein Aufmacher, der achtzig Punkte vor der Bildschirmkante aufhört, nach Fehler aussieht — also trägt `screenMargin` auf tvOS den Überscan mit. Die Karten brauchen einen Fokus-Rückruf, weil `\.isFocused` den nächsten fokussierbaren Vorfahren meint und das in einer Reihe aus Karten nicht verlässlich die Karte ist. Und der Bau scheiterte an „Unable to resolve module dependency: VLCKit" — der Abhängigkeits- Sucher prüft `canImport(FlixyVLC)` auch für ein Ziel, das es nie verlinkt. Ein `&& !os(tvOS)` genügt; das Paket selbst bleibt unverändert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 lines
3.3 KiB
Swift
80 lines
3.3 KiB
Swift
import MediaKit
|
|
|
|
/// Welcher Titel und welches Bild auf eine Kachel gehören.
|
|
///
|
|
/// **Hier und nicht bei einer Ansicht.** Das stand in `MediaRow.swift` — und
|
|
/// damit in einer Datei, die nur das Telefon übersetzt. Der Fernseher zeigt
|
|
/// dieselben Kacheln mit denselben Rückfallketten; sie dort noch einmal
|
|
/// hinzuschreiben hiesse, zwei Stellen gleich zu halten, an denen die Fehler
|
|
/// erst nach Wochen auffallen: ein Plakat, das bei einer Staffel grau bleibt,
|
|
/// sieht nicht nach einem Fehler aus, sondern nach einem Server ohne Bild.
|
|
///
|
|
/// Reine Ableitungen aus `MediaItem`, keine Ansicht — deshalb neben den
|
|
/// übrigen Modellen und nicht in FlixyDesign, das `MediaItem` gar nicht kennt.
|
|
extension MediaItem {
|
|
/// Der Titel auf einer Kachel in einer Reihe.
|
|
///
|
|
/// Groß steht, worum es geht: bei einer Folge ihr eigener Titel, bei
|
|
/// einer Staffel der Name der Serie. Die Einordnung — Serie, Staffel —
|
|
/// gehört darunter. „Staffel 10" allein als Überschrift sagt nichts
|
|
/// darüber, welche Serie gemeint ist.
|
|
var rowTitle: String {
|
|
switch kind {
|
|
case .episode: title
|
|
// Die Serie steht je nach Antwort mal unter `parentTitle`, mal unter
|
|
// `seriesTitle`. Fehlt beides, bleibt „Staffel 2" — besser als
|
|
// gar keine Beschriftung.
|
|
case .season: parentTitle ?? seriesTitle ?? title
|
|
default: 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 ?? seriesThumb
|
|
// Eine Staffel hat bei Jellyfin oft gar kein eigenes Bild — sie leiht
|
|
// sich das der Serie. Ohne diesen Rückfall blieb „Nachtstreife,
|
|
// Staffel 7" eine graue Fläche, obwohl die Serie ein Plakat hat.
|
|
case .season: art ?? thumb ?? seriesArt ?? seriesThumb
|
|
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: seriesThumb ?? parentThumb ?? thumb
|
|
// Das eigene Staffelplakat zuerst: es zeigt die Staffel. Gibt es
|
|
// keines, ist das der Serie allemal besser als nichts.
|
|
case .season: thumb ?? seriesThumb
|
|
default: thumb
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Laufzeiten als Text.
|
|
///
|
|
/// Aus derselben Ansichtsdatei hierher geholt wie die Kachel-Ableitungen
|
|
/// darüber: am Fernseher steht dieselbe Zeile unter demselben Aufmacher.
|
|
extension Duration {
|
|
/// Laufzeit als „2 Std 46 Min" bzw. „46 Min".
|
|
var runtimeLabel: String {
|
|
let totalMinutes = Int(components.seconds / 60)
|
|
let hours = totalMinutes / 60
|
|
let minutes = totalMinutes % 60
|
|
if hours > 0 {
|
|
return String(localized: "runtime.hoursMinutes \(hours) \(minutes)")
|
|
}
|
|
return String(localized: "runtime.minutes \(minutes)")
|
|
}
|
|
}
|