Vorher stand dort eine Liste aller Mediatheken: auf „Bibliothek" tippen, dann auf „Filme", dann endlich ein Raster. In eine andere wechseln hiess zurück und wieder hinein. Der Zwischenbildschirm hat nie etwas erzählt, was eine Leiste nicht auch zeigen kann — er war ein Platzproblem, kein Entwurf. Jetzt steht man sofort in einer Mediathek, und darüber der unteren Leiste liegt eine zweite mit den Mediatheken und Live-TV. Auf dem iPad und am Mac ändert sich nichts: dort steht jede Mediathek einzeln in der Seitenleiste, auf dem Fernseher nebeneinander in der oberen Leiste — es ist dieselbe Idee in der Form, die ein Telefon hergibt. **Warum eine zweite Leiste und nicht Plätze in der unteren.** Die hat fünf, und alles darüber schiebt das System in ein „Mehr"-Verzeichnis, dessen Einträge zwei Navigationsleisten übereinander tragen. Genau daran ist der Zwischenbildschirm seinerzeit entstanden. Eine selbst gezeichnete Leiste kennt diese Grenze nicht — und sie sieht aus wie die Insel auf dem iPad, weil sie dieselbe Fläche benutzt: `FlixySidebar.surface(in:)` ist ein Bauteil, kein nachgebautes Aussehen, und wird jetzt zum dritten Mal gerufen. Als Sicherheitsrand und nicht als Überzug, damit die Liste über der Leiste endet statt dahinter zu verschwinden. Bei mehr als vier Mediatheken wird sie waagerecht scrollbar. **Das Monogramm steht jetzt in der Werkzeugleiste**, am rechten Ende, auf derselben Höhe wie Suche, Filter und Ansicht — die rücken dafür nach links. Als Überzug lag es über dem Inhalt, auf einem Raster also über einem Plakat. Dass es dabei an den Anfang der Reihe rutschte, hat einen Grund, der im Quelltext steht: SwiftUI reiht Werkzeugleisten von aussen nach innen, ein von aussen angehängter Eintrag steht deshalb **vor** den übrigen. Wo in der Reihe etwas steht, kann nur der entscheiden, der die Reihe schreibt. Auf der Startseite bleibt es ein Überzug — dort gibt es keine Navigationsleiste. Dazu: die Zuordnung Mediathekenart → Symbol stand dreimal im Projekt, eine davon falsch (jede Mediathek bekam „film", auch eine mit Serien). Jetzt einmal, neben den übrigen Ableitungen aus den Modellen. Geprüft im Simulator: „Bibliothek" öffnet sofort das Raster, die Leiste wechselt zwischen Filme, Serien, Gemischtes und Live-TV, und das Monogramm steht ganz rechts in der Werkzeugleiste. iOS und tvOS bauen, 469 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
4.0 KiB
Swift
100 lines
4.0 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)")
|
|
}
|
|
}
|
|
|
|
extension MediaLibrary.Kind {
|
|
/// Das Symbol, an dem man eine Mediathek erkennt.
|
|
///
|
|
/// **Eine Zuordnung, nicht drei.** Dieselbe Entscheidung stand bisher an
|
|
/// drei Stellen — in der Seitenleiste, im Zwischenbildschirm und am
|
|
/// Navigationsziel —, und die dritte war falsch: sie gab jeder Mediathek
|
|
/// „film", auch einer mit Serien. Drei Kopien einer Zuordnung halten sich
|
|
/// nicht von allein gleich.
|
|
public var symbol: String {
|
|
switch self {
|
|
case .movie: "film"
|
|
case .show: "tv"
|
|
case .music: "music.note"
|
|
case .photo: "photo"
|
|
case .liveTV: "dot.radiowaves.left.and.right"
|
|
case .other: "rectangle.stack"
|
|
}
|
|
}
|
|
}
|