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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2fcf6821c8
commit
4b9a1e7b67
@@ -0,0 +1,185 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Die Schriftskala der App.
|
||||
///
|
||||
/// Auf iOS die Systemstile, damit Dynamic Type wirkt. Die Rolle steht im
|
||||
/// Namen, nicht die Größe — sonst führt jede Anpassung zu einer Suche quer
|
||||
/// durch alle Views.
|
||||
///
|
||||
/// **Auf dem Mac stehen feste Größen.** Dieselben Stile sind dort deutlich
|
||||
/// kleiner: `largeTitle` misst 26 Punkt statt 34, `body` 13 statt 17. Eine
|
||||
/// Oberfläche, die auf dem iPad stimmt, wirkt damit auf dem Mac zusammen-
|
||||
/// geschrumpft — und genau das war sie. Die Zahlen hier sind die von iOS.
|
||||
///
|
||||
/// Das kostet auf dem Mac Dynamic Type. Das ist vertretbar: macOS hat keine
|
||||
/// systemweite Textgrößen-Einstellung, auf die Apps reagieren müssten, und ein
|
||||
/// Fenster lässt sich stattdessen größer ziehen.
|
||||
///
|
||||
/// **Auf dem Fernseher wieder Systemstile, aber andere Rollen.** Dynamic Type
|
||||
/// gibt es dort auch, und dass `body` grösser ausfällt als am Telefon, ist
|
||||
/// der Sinn der Sache — drei Meter Abstand. Falsch wäre nur, die iOS-Zuordnung
|
||||
/// einfach durchfallen zu lassen: die Skala ist dort anders gespreizt, und
|
||||
/// eine Beschriftung in `subheadline` unter einem Plakat ist aus drei Metern
|
||||
/// Zierrat.
|
||||
extension Font {
|
||||
#if os(macOS)
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
public static let haloHero = Font.system(size: 34, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let haloTitle = Font.system(size: 22, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let haloRowHeader = Font.system(size: 17, weight: .semibold)
|
||||
/// Titel unter einem Poster.
|
||||
public static let haloCardTitle = Font.system(size: 15, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let haloBody = Font.system(size: 17)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let haloCaption = Font.system(size: 12)
|
||||
#elseif os(tvOS)
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
///
|
||||
/// **`.title` und nicht `.largeTitle`.** In SwiftUI gibt es den Stil auf
|
||||
/// tvOS, in UIKit nicht — `UIFontTextStyleLargeTitle` ist dort als nicht
|
||||
/// verfügbar markiert. Was SwiftUI daraus macht, steht nirgends, und eine
|
||||
/// Schrift, deren Grösse niemand nennen kann, gehört nicht in die grösste
|
||||
/// Zeile der App.
|
||||
public static let haloHero = Font.system(.title, design: .default, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let haloTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let haloRowHeader = Font.system(.headline, design: .default, weight: .semibold)
|
||||
/// Titel unter einem Poster — auf tvOS nur bei Fokus sichtbar.
|
||||
///
|
||||
/// Eine Stufe tiefer als auf iOS, und das ist gewollt: auf tvOS ist
|
||||
/// `caption` schon gross, und ein Raster aus sechs Spalten mit
|
||||
/// `subheadline`-Titeln hätte mehr Schrift als Bild.
|
||||
public static let haloCardTitle = Font.system(.caption, design: .default, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let haloBody = Font.system(.body)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let haloCaption = Font.system(.caption2)
|
||||
#else
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
public static let haloHero = Font.system(.largeTitle, design: .default, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let haloTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let haloRowHeader = Font.system(.headline, design: .default, weight: .semibold)
|
||||
/// Titel unter einem Poster.
|
||||
public static let haloCardTitle = Font.system(.subheadline, design: .default, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let haloBody = Font.system(.body)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let haloCaption = Font.system(.caption)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Abstände.
|
||||
///
|
||||
/// Eine Skala statt frei gewählter Zahlen: dadurch stimmen die Ränder zwischen
|
||||
/// Bildschirmen überein, ohne dass man sie einzeln nachmisst.
|
||||
public enum HaloSpacing {
|
||||
#if os(tvOS)
|
||||
// Durchweg doppelt: ein Fernseher ist 1920 Punkte breit und wird aus drei
|
||||
// Metern gelesen. Acht Punkte Abstand sind dort keine Trennung mehr.
|
||||
public static let tight: CGFloat = 8
|
||||
public static let snug: CGFloat = 16
|
||||
public static let regular: CGFloat = 24
|
||||
public static let loose: CGFloat = 32
|
||||
public static let section: CGFloat = 48
|
||||
/// Der seitliche Rand des Inhalts — **vom Bildschirmrand aus**.
|
||||
///
|
||||
/// Achtzig Punkte Überscan plus achtundvierzig Punkte Rinne.
|
||||
///
|
||||
/// **Warum die achtzig hier drinstehen.** SwiftUI legt sie von selbst als
|
||||
/// Sicherheitsrand an, und normalerweise nähme man sie geschenkt. Der
|
||||
/// Aufmacher muss aber bis an die Bildschirmkante reichen — ein Bild, das
|
||||
/// achtzig Punkte vor dem Rand aufhört, sieht auf einem Fernseher nach
|
||||
/// Fehler aus. Also laufen die Seiten dieser App bildfüllend
|
||||
/// (`ignoresSafeArea`), und dann muss der Rand hier wieder dazu.
|
||||
///
|
||||
/// Das heisst zugleich: wer diesen Wert benutzt, ignoriert den
|
||||
/// Sicherheitsrand. Beides gehört zusammen.
|
||||
public static let screenMargin: CGFloat = 128
|
||||
#else
|
||||
public static let tight: CGFloat = 4
|
||||
public static let snug: CGFloat = 8
|
||||
public static let regular: CGFloat = 12
|
||||
public static let loose: CGFloat = 16
|
||||
public static let section: CGFloat = 24
|
||||
/// Der seitliche Rand des Inhalts zum Bildschirmrand.
|
||||
public static let screenMargin: CGFloat = 16
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Der Überscan-Bereich eines Fernsehers.
|
||||
///
|
||||
/// **Was das ist.** Ein Fernseher zeigt nicht zwingend das ganze Bild: je nach
|
||||
/// Gerät und Einstellung schneidet er am Rand ab, und was dort steht, sieht
|
||||
/// niemand.
|
||||
///
|
||||
/// **Nachgemessen, nicht abgeschrieben.** Im Simulator eines Apple TV 4K
|
||||
/// meldet SwiftUI bei 1920×1080 genau diese Ränder — sechzig oben und unten,
|
||||
/// **achtzig** seitlich, Nutzfläche also 1760×960. Die verbreitete Angabe
|
||||
/// „neunzig" stimmt nicht; eine zweite Quelle nannte achtzig und hatte recht.
|
||||
///
|
||||
/// **Und man bekommt sie geschenkt.** SwiftUI legt sie von selbst als
|
||||
/// Sicherheitsrand an — deshalb steht in `HaloSpacing.screenMargin` nur die
|
||||
/// Rinne innerhalb davon. Die Zahl hier ist für die Gegenrichtung: ein
|
||||
/// bildfüllender Aufmacher, der den Sicherheitsrand absichtlich übergeht und
|
||||
/// dessen Schrift trotzdem drinbleiben muss.
|
||||
public enum HaloSafeArea {
|
||||
public static let tv = EdgeInsets(top: 60, leading: 80, bottom: 60, trailing: 80)
|
||||
}
|
||||
|
||||
public enum HaloRadius {
|
||||
/// Die Ecke einer Kachel.
|
||||
///
|
||||
/// Auf dem Fernseher grösser: eine Kachel, die sich beim Fokus anhebt,
|
||||
/// wirkt mit acht Punkten bei 1920 Punkten Breite kantig.
|
||||
#if os(tvOS)
|
||||
public static let card: CGFloat = 12
|
||||
#else
|
||||
public static let card: CGFloat = 8
|
||||
#endif
|
||||
public static let sheet: CGFloat = 16
|
||||
public static let control: CGFloat = 10
|
||||
/// Eine schwebende Fläche — die Seitenleiste. So rund wie das System sie
|
||||
/// auf dem iPad zeichnet; daran misst sich der Nachbau.
|
||||
public static let panel: CGFloat = 22
|
||||
}
|
||||
|
||||
/// Die Seitenverhältnisse, in denen Plex seine Bilder liefert.
|
||||
///
|
||||
/// An einer Stelle, weil ein falscher Wert nicht auffällt, sondern nur
|
||||
/// beschneidet: ein 16:9-Hintergrundbild in einem 3:4-Rahmen verliert links
|
||||
/// und rechts je ein Viertel, und man sieht dem Ergebnis nicht an, dass etwas
|
||||
/// fehlt.
|
||||
public enum HaloAspectRatio {
|
||||
/// Kinoplakate im Hochformat.
|
||||
public static let poster: CGFloat = 2.0 / 3.0
|
||||
/// Szenenbilder und Hintergrundbilder.
|
||||
public static let landscape: CGFloat = 16.0 / 9.0
|
||||
/// Der Aufmacher auf der Startseite — dasselbe wie `landscape`, weil Plex
|
||||
/// dafür `art` liefert.
|
||||
public static let hero: CGFloat = landscape
|
||||
/// Der Aufmacher auf einem schmalen Bildschirm.
|
||||
///
|
||||
/// **Quer, aber beschnitten — und beides hat einen Grund.**
|
||||
///
|
||||
/// Quer, weil dort ein Hintergrundbild liegt. Vorher lag hier ein Plakat
|
||||
/// (2:3), und das ist für einen Aufmacher das falsche Bild: ein Plakat
|
||||
/// trägt den Titel schon selbst, und darüber liegt dann noch der
|
||||
/// freigestellte Schriftzug — zweimal derselbe Name.
|
||||
///
|
||||
/// Beschnitten, weil 16:9 auf einem iPhone keine zweihundertfünfzig
|
||||
/// Punkte Höhe sind, und darin sollen Schriftzug, Zeile mit Jahr und
|
||||
/// Länge, ein Satz Beschreibung und zwei Knöpfe Platz finden. Sie fanden
|
||||
/// ihn nicht und lagen übereinander. 4:3 gibt dem Bild rund ein Drittel
|
||||
/// mehr Höhe; dafür fehlt ihm links und rechts zusammen ein Viertel.
|
||||
public static let heroCompact: CGFloat = 4.0 / 3.0
|
||||
|
||||
/// Sendersymbole. Breiter als hoch, weil Senderlogos Schriftzüge sind —
|
||||
/// quadratisch beschnitten fehlt der halbe Name.
|
||||
public static let channelLogo: CGFloat = 5.0 / 3.0
|
||||
}
|
||||
Reference in New Issue
Block a user