Files
Flaro/Halo/Session/MediaItem+Presentation.swift
T
ScarriffleandClaude Opus 5 4b9a1e7b67 Flixy heißt ab hier Halo
Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit,
damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind.

712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25
Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit
`HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`,
`JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben.

`docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind
ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben
hiesse, die Vergangenheit zu glätten.

**Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis:
`com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte
Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container;
Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu
laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen.

**Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der
Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr
heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter
`…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur
anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim
Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor
dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet.

Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht
statt umbenannt — sie steht ohnehin nicht im Repo.

Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist
ein Handgriff ausserhalb des Projekts.

368 Tests grün. iOS und tvOS bauen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 11:12:32 +02:00

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 HaloDesign, 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"
}
}
}