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
@@ -2,21 +2,21 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlixyKit",
|
||||
name: "HaloKit",
|
||||
// Quellsprache ist Englisch, damit die Schlüssel im Code lesbar bleiben.
|
||||
// Deutsch kommt als Übersetzung dazu; die Systemsprache entscheidet.
|
||||
defaultLocalization: "en",
|
||||
// macOS steht hier nur, damit `swift test` die Logik-Tests ohne
|
||||
// Simulator laufen lassen kann — das spart bei jedem Durchlauf Minuten.
|
||||
// Welche Plattformen die Apps bedienen, legt project.yml fest: iOS für
|
||||
// Flixy, tvOS für FlixyTV.
|
||||
// Halo, tvOS für HaloTV.
|
||||
platforms: [.iOS(.v26), .macOS(.v26), .tvOS(.v26)],
|
||||
products: [
|
||||
.library(name: "MediaKit", targets: ["MediaKit"]),
|
||||
.library(name: "JellyfinKit", targets: ["JellyfinKit"]),
|
||||
.library(name: "FlixyDesign", targets: ["FlixyDesign"]),
|
||||
.library(name: "FlixyPlayback", targets: ["FlixyPlayback"]),
|
||||
.library(name: "FlixyDownloads", targets: ["FlixyDownloads"]),
|
||||
.library(name: "HaloDesign", targets: ["HaloDesign"]),
|
||||
.library(name: "HaloPlayback", targets: ["HaloPlayback"]),
|
||||
.library(name: "HaloDownloads", targets: ["HaloDownloads"]),
|
||||
],
|
||||
targets: [
|
||||
// Sprechen mit dem Server: Anmeldung, Endpunkte, Modelle. Kein SwiftUI,
|
||||
@@ -42,21 +42,21 @@ let package = Package(
|
||||
// Ein Werkzeug für die Fragen, die reines HTTP sind — ohne Oberfläche,
|
||||
// ohne Gerät, ohne Upload. Eine Runde dauert damit Sekunden statt
|
||||
// eines halben Tages, und das Upload-Kontingent bleibt für Builds.
|
||||
.executableTarget(name: "FlixyProbe", dependencies: ["MediaKit", "JellyfinKit"]),
|
||||
.executableTarget(name: "HaloProbe", dependencies: ["MediaKit", "JellyfinKit"]),
|
||||
|
||||
// Farben, Typografie, wiederkehrende Bausteine. Hängt bewusst von
|
||||
// nichts ab: dadurch bleibt das Umstellen der Akzentfarbe ein
|
||||
// Token-Wechsel und keine Suche quer durch die App.
|
||||
.target(name: "FlixyDesign", resources: [.process("Localizable.xcstrings")]),
|
||||
.testTarget(name: "FlixyDesignTests", dependencies: ["FlixyDesign"]),
|
||||
.target(name: "HaloDesign", resources: [.process("Localizable.xcstrings")]),
|
||||
.testTarget(name: "HaloDesignTests", dependencies: ["HaloDesign"]),
|
||||
|
||||
// Die Wiedergabe-Abstraktion samt AVPlayer-Engine und der Entscheidung
|
||||
// direkt / umpacken / umrechnen.
|
||||
.target(name: "FlixyPlayback", dependencies: ["MediaKit"]),
|
||||
.testTarget(name: "FlixyPlaybackTests", dependencies: ["FlixyPlayback"]),
|
||||
.target(name: "HaloPlayback", dependencies: ["MediaKit"]),
|
||||
.testTarget(name: "HaloPlaybackTests", dependencies: ["HaloPlayback"]),
|
||||
|
||||
// Warteschlange, Hintergrundübertragung, Offline-Bestand.
|
||||
.target(name: "FlixyDownloads", dependencies: ["MediaKit"], resources: [.process("Localizable.xcstrings")]),
|
||||
.testTarget(name: "FlixyDownloadsTests", dependencies: ["FlixyDownloads"]),
|
||||
.target(name: "HaloDownloads", dependencies: ["MediaKit"], resources: [.process("Localizable.xcstrings")]),
|
||||
.testTarget(name: "HaloDownloadsTests", dependencies: ["HaloDownloads"]),
|
||||
]
|
||||
)
|
||||
+7
-7
@@ -4,7 +4,7 @@ extension EnvironmentValues {
|
||||
/// Die aktuelle Akzentfarbe, bereits kontrastkorrigiert.
|
||||
///
|
||||
/// Bewusst eine schlichte `Color` in der Umgebung statt einer Abhängigkeit
|
||||
/// auf `FlixyTheme`: Bausteine wie `WatchProgressBar` sollen sich in einer
|
||||
/// auf `HaloTheme`: Bausteine wie `WatchProgressBar` sollen sich in einer
|
||||
/// Vorschau mit einer beliebigen Farbe zeigen lassen, ohne dass dafür ein
|
||||
/// ganzes Theme-Objekt gebaut werden muss.
|
||||
/// Der Vorgabewert liest die gespeicherte Auswahl, statt blind die
|
||||
@@ -14,19 +14,19 @@ extension EnvironmentValues {
|
||||
/// Formulare zeichnen Zeilen teilweise in einem eigenen Kontext. Vorher
|
||||
/// leuchtete dort die Standardfarbe, während daneben schon die gewählte
|
||||
/// stand; jetzt stimmen beide.
|
||||
@Entry public var flixyAccent: Color = AccentOption.storedAccentColor()
|
||||
@Entry public var haloAccent: Color = AccentOption.storedAccentColor()
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Setzt die Akzentfarbe für diesen Teilbaum.
|
||||
///
|
||||
/// Wird einmal an der Wurzel aus `FlixyTheme` gesetzt; darunter greift
|
||||
/// Wird einmal an der Wurzel aus `HaloTheme` gesetzt; darunter greift
|
||||
/// alles automatisch darauf zu.
|
||||
public func flixyAccent(_ color: Color) -> some View {
|
||||
environment(\.flixyAccent, color)
|
||||
public func haloAccent(_ color: Color) -> some View {
|
||||
environment(\.haloAccent, color)
|
||||
}
|
||||
|
||||
public func flixyAccent(_ theme: FlixyTheme) -> some View {
|
||||
environment(\.flixyAccent, theme.accentColor)
|
||||
public func haloAccent(_ theme: HaloTheme) -> some View {
|
||||
environment(\.haloAccent, theme.accentColor)
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -3,7 +3,7 @@ import SwiftUI
|
||||
|
||||
/// Eine wählbare Akzentfarbe.
|
||||
///
|
||||
/// Die kuratierten Werte sind so gewählt, dass sie auf `FlixyColor.canvas`
|
||||
/// Die kuratierten Werte sind so gewählt, dass sie auf `HaloColor.canvas`
|
||||
/// mindestens 4,5:1 erreichen — geprüft von `AccentPaletteTests`, damit beim
|
||||
/// Nachlegen einer Farbe nicht versehentlich etwas Unlesbares hineinrutscht.
|
||||
public struct AccentOption: Sendable, Identifiable {
|
||||
@@ -66,13 +66,13 @@ extension AccentOption {
|
||||
extension AccentOption {
|
||||
/// Wo die Auswahl liegt.
|
||||
///
|
||||
/// Hier statt in `FlixyTheme`, weil zwei Stellen sie brauchen: das Theme
|
||||
/// Hier statt in `HaloTheme`, weil zwei Stellen sie brauchen: das Theme
|
||||
/// und der Vorgabewert der Umgebung. Zwei Kopien derselben Schlüssel
|
||||
/// driften auseinander, und zwar unbemerkt — es fällt erst auf, wenn eine
|
||||
/// Farbe irgendwo nicht mitkommt.
|
||||
enum StorageKey {
|
||||
static let identifier = "flixy.accent.identifier"
|
||||
static let customHex = "flixy.accent.customHex"
|
||||
static let identifier = "halo.accent.identifier"
|
||||
static let customHex = "halo.accent.customHex"
|
||||
}
|
||||
|
||||
/// Die gespeicherte Auswahl, oder die Voreinstellung.
|
||||
@@ -102,6 +102,6 @@ extension AccentOption {
|
||||
|
||||
/// Die gespeicherte Farbe, bereits kontrastkorrigiert.
|
||||
public static func storedAccentColor(in defaults: UserDefaults = .standard) -> Color {
|
||||
Contrast.legible(stored(in: defaults).color, on: FlixyColor.canvas).swiftUIColor
|
||||
Contrast.legible(stored(in: defaults).color, on: HaloColor.canvas).swiftUIColor
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ import Foundation
|
||||
|
||||
/// Kontrastrechnung nach WCAG 2.1.
|
||||
///
|
||||
/// Gebraucht wird sie für die eigene Akzentfarbe: Flixy lässt jede Farbe zu,
|
||||
/// Gebraucht wird sie für die eigene Akzentfarbe: Halo lässt jede Farbe zu,
|
||||
/// aber ein dunkles Marineblau auf fast schwarzem Grund ist als Schrift oder
|
||||
/// Symbol unlesbar. `legible(_:on:)` hebt solche Farben so weit an, bis sie
|
||||
/// den geforderten Kontrast erreichen — und lässt alles in Ruhe, was ihn
|
||||
+13
-13
@@ -2,10 +2,10 @@ import SwiftUI
|
||||
|
||||
/// Die Flächen- und Schriftfarben der App.
|
||||
///
|
||||
/// Flixy kennt nur den dunklen Modus — es gibt deshalb bewusst keine
|
||||
/// Halo kennt nur den dunklen Modus — es gibt deshalb bewusst keine
|
||||
/// Hell-/Dunkel-Paare, sondern je einen festen Wert. Die einzige Farbe, die
|
||||
/// sich zur Laufzeit ändert, ist der Akzent; er kommt aus `FlixyTheme`.
|
||||
public enum FlixyColor {
|
||||
/// sich zur Laufzeit ändert, ist der Akzent; er kommt aus `HaloTheme`.
|
||||
public enum HaloColor {
|
||||
// Flächen, von hinten nach vorn gestaffelt.
|
||||
/// Der Seitenhintergrund.
|
||||
public static let canvas = RGB(hex: 0x0B0B0D)
|
||||
@@ -28,18 +28,18 @@ public enum FlixyColor {
|
||||
}
|
||||
|
||||
extension Color {
|
||||
public static let flixyCanvas = FlixyColor.canvas.swiftUIColor
|
||||
public static let flixySurface = FlixyColor.surface.swiftUIColor
|
||||
public static let flixyElevated = FlixyColor.elevated.swiftUIColor
|
||||
public static let flixyPlaceholder = FlixyColor.placeholder.swiftUIColor
|
||||
public static let flixyTextPrimary = FlixyColor.textPrimary.swiftUIColor
|
||||
public static let flixyTextSecondary = FlixyColor.textSecondary.swiftUIColor
|
||||
public static let flixyTextMuted = FlixyColor.textMuted.swiftUIColor
|
||||
public static let flixyDestructive = FlixyColor.destructive.swiftUIColor
|
||||
public static let haloCanvas = HaloColor.canvas.swiftUIColor
|
||||
public static let haloSurface = HaloColor.surface.swiftUIColor
|
||||
public static let haloElevated = HaloColor.elevated.swiftUIColor
|
||||
public static let haloPlaceholder = HaloColor.placeholder.swiftUIColor
|
||||
public static let haloTextPrimary = HaloColor.textPrimary.swiftUIColor
|
||||
public static let haloTextSecondary = HaloColor.textSecondary.swiftUIColor
|
||||
public static let haloTextMuted = HaloColor.textMuted.swiftUIColor
|
||||
public static let haloDestructive = HaloColor.destructive.swiftUIColor
|
||||
|
||||
/// Trennlinien und Ränder. Als Weiß mit Deckkraft statt als fester Wert,
|
||||
/// damit sie auf jeder Flächenstufe gleich zurückhaltend wirken.
|
||||
public static let flixySeparator = Color.white.opacity(0.10)
|
||||
public static let haloSeparator = Color.white.opacity(0.10)
|
||||
/// Die Abdunklung hinter der ausgefahrenen Seitenleiste.
|
||||
public static let flixyScrim = Color.black.opacity(0.55)
|
||||
public static let haloScrim = Color.black.opacity(0.55)
|
||||
}
|
||||
+4
-4
@@ -7,7 +7,7 @@ import SwiftUI
|
||||
/// Startseite zeigt dutzende Bilder gleichzeitig, und `AsyncImage` lädt sie bei
|
||||
/// jedem Erscheinen neu. Auf dem Mac wurde daraus ein sichtbarer Fehler: der
|
||||
/// Aufmacher und die Hälfte der Plakate blieben grau.
|
||||
public struct FlixyArtwork: View {
|
||||
public struct HaloArtwork: View {
|
||||
/// Die Adressen, der Reihe nach probiert.
|
||||
///
|
||||
/// **Warum mehrere.** Eine Folge hat selten ein eigenes Querformatbild,
|
||||
@@ -90,9 +90,9 @@ public struct FlixyArtwork: View {
|
||||
@ViewBuilder
|
||||
private var placeholder: some View {
|
||||
if fillsFrame {
|
||||
Rectangle().fill(Color.flixyPlaceholder)
|
||||
Rectangle().fill(Color.haloPlaceholder)
|
||||
} else {
|
||||
Rectangle().fill(Color.flixyPlaceholder).aspectRatio(aspectRatio, contentMode: .fit)
|
||||
Rectangle().fill(Color.haloPlaceholder).aspectRatio(aspectRatio, contentMode: .fit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public struct FlixyArtwork: View {
|
||||
// mehr Lärm als Hilfe. Das Symbol sagt genug.
|
||||
Image(systemName: fallbackSymbol)
|
||||
.font(.title2)
|
||||
.foregroundStyle(Color.flixyTextMuted)
|
||||
.foregroundStyle(Color.haloTextMuted)
|
||||
} else {
|
||||
ShimmerPlaceholder()
|
||||
}
|
||||
+16
-16
@@ -81,7 +81,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
// 3:4 — mit `scaledToFill` wurde das Bild dadurch links und rechts
|
||||
// kräftig beschnitten, und vom Aufmacher war nur ein Ausschnitt zu
|
||||
// sehen.
|
||||
aspectRatio: CGFloat = FlixyAspectRatio.hero,
|
||||
aspectRatio: CGFloat = HaloAspectRatio.hero,
|
||||
fillsAvailableSpace: Bool = false,
|
||||
@ViewBuilder actions: () -> Actions
|
||||
) {
|
||||
@@ -114,16 +114,16 @@ public struct HeroBanner<Actions: View>: View {
|
||||
|
||||
/// Das Bild — und, solange gewechselt wird, das vorige darunter.
|
||||
///
|
||||
/// Ohne Wechsel ist es genau ein `FlixyArtwork` wie zuvor: die zweite
|
||||
/// Ohne Wechsel ist es genau ein `HaloArtwork` wie zuvor: die zweite
|
||||
/// Ebene entsteht nur, wenn jemand eine vorige Adresse mitgibt.
|
||||
@ViewBuilder
|
||||
private var bild: some View {
|
||||
if outgoingArtworkURLs.isEmpty {
|
||||
FlixyArtwork(urls: artworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
HaloArtwork(urls: artworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
} else {
|
||||
ZStack {
|
||||
FlixyArtwork(urls: outgoingArtworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
FlixyArtwork(urls: artworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
HaloArtwork(urls: outgoingArtworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
HaloArtwork(urls: artworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
.opacity(crossfade)
|
||||
.offset(x: incomingOffset)
|
||||
}
|
||||
@@ -166,8 +166,8 @@ public struct HeroBanner<Actions: View>: View {
|
||||
LinearGradient(
|
||||
stops: [
|
||||
.init(color: .clear, location: 0),
|
||||
.init(color: Color.flixyCanvas.opacity(0.75), location: 0.45),
|
||||
.init(color: Color.flixyCanvas, location: 1),
|
||||
.init(color: Color.haloCanvas.opacity(0.75), location: 0.45),
|
||||
.init(color: Color.haloCanvas, location: 1),
|
||||
],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
@@ -250,7 +250,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
logo: PlatformImage?,
|
||||
scale: CGFloat
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: FlixySpacing.snug * scale) {
|
||||
VStack(alignment: .leading, spacing: HaloSpacing.snug * scale) {
|
||||
if let logo {
|
||||
Image(platform: logo)
|
||||
.resizable()
|
||||
@@ -266,24 +266,24 @@ public struct HeroBanner<Actions: View>: View {
|
||||
// eine Systemschrift.
|
||||
Text(title)
|
||||
.font(.system(size: 34 * scale, weight: .bold))
|
||||
.foregroundStyle(Color.flixyTextPrimary)
|
||||
.foregroundStyle(Color.haloTextPrimary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
if let metadataContent {
|
||||
metadataContent
|
||||
.font(.system(size: 12 * scale))
|
||||
.foregroundStyle(Color.flixyTextSecondary)
|
||||
.foregroundStyle(Color.haloTextSecondary)
|
||||
} else if let metadata {
|
||||
Text(metadata)
|
||||
.font(.system(size: 12 * scale))
|
||||
.foregroundStyle(Color.flixyTextSecondary)
|
||||
.foregroundStyle(Color.haloTextSecondary)
|
||||
}
|
||||
|
||||
if let summary {
|
||||
Text(summary)
|
||||
.font(.system(size: 12 * scale))
|
||||
.foregroundStyle(Color.flixyTextSecondary)
|
||||
.foregroundStyle(Color.haloTextSecondary)
|
||||
.lineLimit(2)
|
||||
// Ein Satz über die ganze Breite eines großen Fensters ist
|
||||
// unlesbar; das Auge verliert die Zeile. Achtzig Zeichen
|
||||
@@ -291,12 +291,12 @@ public struct HeroBanner<Actions: View>: View {
|
||||
.frame(maxWidth: 640 * scale, alignment: .leading)
|
||||
}
|
||||
|
||||
HStack(spacing: FlixySpacing.regular) {
|
||||
HStack(spacing: HaloSpacing.regular) {
|
||||
actions
|
||||
}
|
||||
.padding(.top, FlixySpacing.tight)
|
||||
.padding(.top, HaloSpacing.tight)
|
||||
}
|
||||
.padding(.horizontal, FlixySpacing.screenMargin * scale)
|
||||
.padding(.bottom, FlixySpacing.loose * scale)
|
||||
.padding(.horizontal, HaloSpacing.screenMargin * scale)
|
||||
.padding(.bottom, HaloSpacing.loose * scale)
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -20,24 +20,24 @@ public struct LandscapeCard: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: FlixySpacing.snug) {
|
||||
FlixyArtwork(url: artworkURL, aspectRatio: Self.aspectRatio, fallbackSymbol: "play.rectangle")
|
||||
VStack(alignment: .leading, spacing: HaloSpacing.snug) {
|
||||
HaloArtwork(url: artworkURL, aspectRatio: Self.aspectRatio, fallbackSymbol: "play.rectangle")
|
||||
.overlay(alignment: .bottom) {
|
||||
if let progress {
|
||||
WatchProgressBar(fraction: progress)
|
||||
}
|
||||
}
|
||||
.clipShape(.rect(cornerRadius: FlixyRadius.card))
|
||||
.clipShape(.rect(cornerRadius: HaloRadius.card))
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.font(.flixyCardTitle)
|
||||
.foregroundStyle(Color.flixyTextPrimary)
|
||||
.font(.haloCardTitle)
|
||||
.foregroundStyle(Color.haloTextPrimary)
|
||||
.lineLimit(1)
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.font(.flixyCaption)
|
||||
.foregroundStyle(Color.flixyTextMuted)
|
||||
.font(.haloCaption)
|
||||
.foregroundStyle(Color.haloTextMuted)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -30,18 +30,18 @@ public struct PlaceholderCard: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: FlixySpacing.snug) {
|
||||
RoundedRectangle(cornerRadius: FlixyRadius.card)
|
||||
.fill(Color.flixyElevated)
|
||||
VStack(alignment: .leading, spacing: HaloSpacing.snug) {
|
||||
RoundedRectangle(cornerRadius: HaloRadius.card)
|
||||
.fill(Color.haloElevated)
|
||||
.aspectRatio(PosterCard.aspectRatio, contentMode: .fit)
|
||||
|
||||
// Zwei Balken an der Stelle von Titel und Jahr. Ohne sie springt
|
||||
// die Zeile, sobald der echte Titel kommt.
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.flixyElevated)
|
||||
.fill(Color.haloElevated)
|
||||
.frame(height: 10)
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.flixyElevated)
|
||||
.fill(Color.haloElevated)
|
||||
.frame(width: 32, height: 8)
|
||||
}
|
||||
.opacity(isBright ? 0.85 : 0.45)
|
||||
+8
-8
@@ -19,25 +19,25 @@ public struct PosterCard: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: FlixySpacing.snug) {
|
||||
FlixyArtwork(url: artworkURL, aspectRatio: Self.aspectRatio)
|
||||
.clipShape(.rect(cornerRadius: FlixyRadius.card))
|
||||
VStack(alignment: .leading, spacing: HaloSpacing.snug) {
|
||||
HaloArtwork(url: artworkURL, aspectRatio: Self.aspectRatio)
|
||||
.clipShape(.rect(cornerRadius: HaloRadius.card))
|
||||
.overlay(alignment: .bottom) {
|
||||
if let progress {
|
||||
WatchProgressBar(fraction: progress)
|
||||
}
|
||||
}
|
||||
.clipShape(.rect(cornerRadius: FlixyRadius.card))
|
||||
.clipShape(.rect(cornerRadius: HaloRadius.card))
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.font(.flixyCardTitle)
|
||||
.foregroundStyle(Color.flixyTextPrimary)
|
||||
.font(.haloCardTitle)
|
||||
.foregroundStyle(Color.haloTextPrimary)
|
||||
.lineLimit(1)
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.font(.flixyCaption)
|
||||
.foregroundStyle(Color.flixyTextMuted)
|
||||
.font(.haloCaption)
|
||||
.foregroundStyle(Color.haloTextMuted)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ public struct ShimmerPlaceholder: View {
|
||||
|
||||
public var body: some View {
|
||||
Rectangle()
|
||||
.fill(Color.flixyPlaceholder)
|
||||
.fill(Color.haloPlaceholder)
|
||||
.overlay {
|
||||
if !reduceMotion {
|
||||
GeometryReader { proxy in
|
||||
+9
-9
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
/// `scaleEffect` und ein Schatten — sieht auf den ersten Blick gleich aus und
|
||||
/// auf den zweiten falsch: die Parallaxe ist der Teil, den man wiedererkennt.
|
||||
///
|
||||
/// Liegt in FlixyDesign und nicht im Fernseh-Ziel, aus demselben Grund wie
|
||||
/// Liegt in HaloDesign und nicht im Fernseh-Ziel, aus demselben Grund wie
|
||||
/// `PosterCard`: das Paket kennt weder `MediaItem` noch die Wiedergabe, und
|
||||
/// das soll so bleiben. Herein kommen Zeichenketten und Adressen.
|
||||
public struct TVPosterCard: View {
|
||||
@@ -100,7 +100,7 @@ public struct TVLandscapeCard: View {
|
||||
|
||||
public var body: some View {
|
||||
TVCard(
|
||||
aspectRatio: FlixyAspectRatio.landscape,
|
||||
aspectRatio: HaloAspectRatio.landscape,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
artworkURL: artworkURL,
|
||||
@@ -124,13 +124,13 @@ private struct TVCard: View {
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: FlixySpacing.snug) {
|
||||
VStack(alignment: .leading, spacing: HaloSpacing.snug) {
|
||||
Button(action: action) {
|
||||
FlixyArtwork(url: artworkURL, aspectRatio: aspectRatio)
|
||||
HaloArtwork(url: artworkURL, aspectRatio: aspectRatio)
|
||||
.overlay(alignment: .bottom) {
|
||||
if let progress { WatchProgressBar(fraction: progress) }
|
||||
}
|
||||
.clipShape(.rect(cornerRadius: FlixyRadius.card))
|
||||
.clipShape(.rect(cornerRadius: HaloRadius.card))
|
||||
}
|
||||
.buttonStyle(.card)
|
||||
.focused($isFocused)
|
||||
@@ -143,13 +143,13 @@ private struct TVCard: View {
|
||||
// Metern eine Textwand.
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.font(.flixyCardTitle)
|
||||
.foregroundStyle(Color.flixyTextPrimary)
|
||||
.font(.haloCardTitle)
|
||||
.foregroundStyle(Color.haloTextPrimary)
|
||||
.lineLimit(1)
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.font(.flixyCaption)
|
||||
.foregroundStyle(Color.flixyTextMuted)
|
||||
.font(.haloCaption)
|
||||
.foregroundStyle(Color.haloTextMuted)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ public struct WatchProgressBar: View {
|
||||
/// 0…1. Werte außerhalb werden abgeschnitten, damit ein fehlerhafter
|
||||
/// Serverwert den Balken nicht über die Kachel hinausschieben kann.
|
||||
private let fraction: Double
|
||||
@Environment(\.flixyAccent) private var accent
|
||||
@Environment(\.haloAccent) private var accent
|
||||
|
||||
public init(fraction: Double) {
|
||||
self.fraction = fraction.clamped()
|
||||
+23
-23
@@ -24,17 +24,17 @@ import SwiftUI
|
||||
extension Font {
|
||||
#if os(macOS)
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
public static let flixyHero = Font.system(size: 34, weight: .bold)
|
||||
public static let haloHero = Font.system(size: 34, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let flixyTitle = Font.system(size: 22, weight: .semibold)
|
||||
public static let haloTitle = Font.system(size: 22, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let flixyRowHeader = Font.system(size: 17, weight: .semibold)
|
||||
public static let haloRowHeader = Font.system(size: 17, weight: .semibold)
|
||||
/// Titel unter einem Poster.
|
||||
public static let flixyCardTitle = Font.system(size: 15, weight: .medium)
|
||||
public static let haloCardTitle = Font.system(size: 15, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let flixyBody = Font.system(size: 17)
|
||||
public static let haloBody = Font.system(size: 17)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let flixyCaption = Font.system(size: 12)
|
||||
public static let haloCaption = Font.system(size: 12)
|
||||
#elseif os(tvOS)
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
///
|
||||
@@ -43,34 +43,34 @@ extension Font {
|
||||
/// 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 flixyHero = Font.system(.title, design: .default, weight: .bold)
|
||||
public static let haloHero = Font.system(.title, design: .default, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let flixyTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
public static let haloTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let flixyRowHeader = Font.system(.headline, design: .default, weight: .semibold)
|
||||
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 flixyCardTitle = Font.system(.caption, design: .default, weight: .medium)
|
||||
public static let haloCardTitle = Font.system(.caption, design: .default, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let flixyBody = Font.system(.body)
|
||||
public static let haloBody = Font.system(.body)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let flixyCaption = Font.system(.caption2)
|
||||
public static let haloCaption = Font.system(.caption2)
|
||||
#else
|
||||
/// Der Titel im Aufmacher-Bild.
|
||||
public static let flixyHero = Font.system(.largeTitle, design: .default, weight: .bold)
|
||||
public static let haloHero = Font.system(.largeTitle, design: .default, weight: .bold)
|
||||
/// Titel auf Detailseiten.
|
||||
public static let flixyTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
public static let haloTitle = Font.system(.title2, design: .default, weight: .semibold)
|
||||
/// Überschrift einer Reihe auf der Startseite.
|
||||
public static let flixyRowHeader = Font.system(.headline, design: .default, weight: .semibold)
|
||||
public static let haloRowHeader = Font.system(.headline, design: .default, weight: .semibold)
|
||||
/// Titel unter einem Poster.
|
||||
public static let flixyCardTitle = Font.system(.subheadline, design: .default, weight: .medium)
|
||||
public static let haloCardTitle = Font.system(.subheadline, design: .default, weight: .medium)
|
||||
/// Fließtext, Beschreibungen.
|
||||
public static let flixyBody = Font.system(.body)
|
||||
public static let haloBody = Font.system(.body)
|
||||
/// Jahreszahl, Laufzeit, Auflösung — alles, was neben dem Titel steht.
|
||||
public static let flixyCaption = Font.system(.caption)
|
||||
public static let haloCaption = Font.system(.caption)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ extension Font {
|
||||
///
|
||||
/// Eine Skala statt frei gewählter Zahlen: dadurch stimmen die Ränder zwischen
|
||||
/// Bildschirmen überein, ohne dass man sie einzeln nachmisst.
|
||||
public enum FlixySpacing {
|
||||
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.
|
||||
@@ -124,15 +124,15 @@ public enum FlixySpacing {
|
||||
/// „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 `FlixySpacing.screenMargin` nur die
|
||||
/// 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 FlixySafeArea {
|
||||
public enum HaloSafeArea {
|
||||
public static let tv = EdgeInsets(top: 60, leading: 80, bottom: 60, trailing: 80)
|
||||
}
|
||||
|
||||
public enum FlixyRadius {
|
||||
public enum HaloRadius {
|
||||
/// Die Ecke einer Kachel.
|
||||
///
|
||||
/// Auf dem Fernseher grösser: eine Kachel, die sich beim Fokus anhebt,
|
||||
@@ -155,7 +155,7 @@ public enum FlixyRadius {
|
||||
/// 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 FlixyAspectRatio {
|
||||
public enum HaloAspectRatio {
|
||||
/// Kinoplakate im Hochformat.
|
||||
public static let poster: CGFloat = 2.0 / 3.0
|
||||
/// Szenenbilder und Hintergrundbilder.
|
||||
+2
-2
@@ -7,7 +7,7 @@ import SwiftUI
|
||||
/// darauf zu achten.
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class FlixyTheme {
|
||||
public final class HaloTheme {
|
||||
private let defaults: UserDefaults
|
||||
|
||||
/// Was in den Einstellungen ausgewählt ist — unkorrigiert, damit die
|
||||
@@ -24,7 +24,7 @@ public final class FlixyTheme {
|
||||
/// Bei den kuratierten Werten ändert die Korrektur nichts — sie erfüllen
|
||||
/// den Grenzwert bereits. Sie greift nur bei eigenen Farben.
|
||||
public var accent: RGB {
|
||||
Contrast.legible(selection.color, on: FlixyColor.canvas)
|
||||
Contrast.legible(selection.color, on: HaloColor.canvas)
|
||||
}
|
||||
|
||||
public var accentColor: Color { accent.swiftUIColor }
|
||||
+1
-1
@@ -24,7 +24,7 @@ public struct MiniPlayerPlacement: Sendable, Equatable {
|
||||
/// Fehler statt wie eine Entscheidung.
|
||||
public let inset: CGFloat
|
||||
|
||||
public init(bounds: CGSize, tile: CGSize, inset: CGFloat = FlixySpacing.regular) {
|
||||
public init(bounds: CGSize, tile: CGSize, inset: CGFloat = HaloSpacing.regular) {
|
||||
self.bounds = bounds
|
||||
self.tile = tile
|
||||
self.inset = inset
|
||||
+14
-14
@@ -13,7 +13,7 @@ import AppKit
|
||||
/// nirgends sonst. Sonst wandert er in zwanzig Dateien und wird beim
|
||||
/// nächsten Bildschirm vergessen.
|
||||
/// Auf was für einem Gerät läuft das hier gerade?
|
||||
public enum FlixyPlatform {
|
||||
public enum HaloPlatform {
|
||||
/// Läuft die App auf einem Mac?
|
||||
///
|
||||
/// **Zwei Wege führen dorthin, und für den Übersetzer sehen sie
|
||||
@@ -43,7 +43,7 @@ public enum FlixyPlatform {
|
||||
#elseif os(macOS)
|
||||
true
|
||||
#else
|
||||
#error("FlixyPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
|
||||
#error("HaloPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
|
||||
#endif
|
||||
}()
|
||||
|
||||
@@ -60,7 +60,7 @@ public enum FlixyPlatform {
|
||||
#elseif os(iOS) || os(macOS)
|
||||
false
|
||||
#else
|
||||
#error("FlixyPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
|
||||
#error("HaloPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public enum FlixyPlatform {
|
||||
extension View {
|
||||
/// Eine schmale Titelzeile — auf dem Mac und auf dem Fernseher gibt es
|
||||
/// sie nicht. Beide fallen in denselben `#else`-Zweig, und beide zu Recht.
|
||||
public func flixyInlineTitle() -> some View {
|
||||
public func haloInlineTitle() -> some View {
|
||||
#if os(iOS)
|
||||
navigationBarTitleDisplayMode(.inline)
|
||||
#else
|
||||
@@ -80,7 +80,7 @@ extension View {
|
||||
///
|
||||
/// Auf dem Mac ist ein Sheet ein Fenster: dort zählt keine Rastung,
|
||||
/// sondern eine vernünftige Mindestgröße.
|
||||
public func flixyMediumSheet() -> some View {
|
||||
public func haloMediumSheet() -> some View {
|
||||
#if os(tvOS)
|
||||
// Auf dem Fernseher ist ein Sheet immer bildfüllend. `presentationDetents`
|
||||
// übersetzt dort zwar — es gibt sie seit tvOS 16 —, bewirkt aber
|
||||
@@ -92,18 +92,18 @@ extension View {
|
||||
// macOS griff nie: auf dem Mac läuft der iOS-Bau, und damit bekam ein
|
||||
// grosses Fenster eine Sheet-Kante in iPhone-Höhe — ein schmaler
|
||||
// Streifen, der unten im Nichts klebt.
|
||||
presentationDetents(FlixyPlatform.isMac ? [.large] : [.medium])
|
||||
presentationDetents(HaloPlatform.isMac ? [.large] : [.medium])
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Dasselbe, aber auf Wunsch bis zur vollen Höhe aufziehbar.
|
||||
public func flixyResizableSheet() -> some View {
|
||||
public func haloResizableSheet() -> some View {
|
||||
#if os(tvOS)
|
||||
self
|
||||
#else
|
||||
// Siehe oben. Auf dem Mac gibt es kein „halb aufgezogen": ein Fenster
|
||||
// hat die Höhe, die es hat, und eine Liste soll sie nutzen.
|
||||
presentationDetents(FlixyPlatform.isMac ? [.large] : [.medium, .large])
|
||||
presentationDetents(HaloPlatform.isMac ? [.large] : [.medium, .large])
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ extension View {
|
||||
/// Stil ist derselbe, den iOS ohnehin nimmt — und der Grund, warum die
|
||||
/// Einstellungen auf dem Mac wie ein Systemdialog aus einem anderen
|
||||
/// Jahrzehnt aussahen.
|
||||
public func flixyForm() -> some View {
|
||||
public func haloForm() -> some View {
|
||||
#if os(macOS)
|
||||
formStyle(.grouped)
|
||||
#else
|
||||
@@ -130,7 +130,7 @@ extension View {
|
||||
///
|
||||
/// Auf tvOS ebenfalls der `#else`-Zweig: die Bildschirmtastatur dort
|
||||
/// kennt weder `keyboardType` noch Autokorrektur in dieser Form.
|
||||
public func flixyPlainEntry(isAddress: Bool = false) -> some View {
|
||||
public func haloPlainEntry(isAddress: Bool = false) -> some View {
|
||||
#if os(iOS)
|
||||
self
|
||||
.keyboardType(isAddress ? .URL : .default)
|
||||
@@ -149,7 +149,7 @@ extension View {
|
||||
/// Leiste verschleierte die Hälfte davon. Auf dem Mac und auf dem
|
||||
/// Fernseher gibt es die Leiste in dieser Form nicht — beide fallen in
|
||||
/// den `#else`-Zweig.
|
||||
public func flixyClearToolbarBackground() -> some View {
|
||||
public func haloClearToolbarBackground() -> some View {
|
||||
#if os(iOS)
|
||||
self
|
||||
.toolbarBackground(.hidden, for: .navigationBar)
|
||||
@@ -164,7 +164,7 @@ extension View {
|
||||
///
|
||||
/// UIKit und AppKit heißen verschieden und meinen dasselbe; die Diagnose soll
|
||||
/// deswegen nicht mit `#if` beginnen.
|
||||
public enum FlixyClipboard {
|
||||
public enum HaloClipboard {
|
||||
public static func copy(_ text: String) {
|
||||
#if os(tvOS)
|
||||
// Es gibt keine. `UIPasteboard` ist auf tvOS als nicht verfügbar
|
||||
@@ -186,7 +186,7 @@ public enum FlixyClipboard {
|
||||
|
||||
extension ToolbarItemPlacement {
|
||||
/// Rechts in der Titelzeile.
|
||||
public static var flixyTrailing: ToolbarItemPlacement {
|
||||
public static var haloTrailing: ToolbarItemPlacement {
|
||||
#if os(iOS) || os(tvOS)
|
||||
.topBarTrailing
|
||||
#else
|
||||
@@ -195,7 +195,7 @@ extension ToolbarItemPlacement {
|
||||
}
|
||||
|
||||
/// Links in der Titelzeile.
|
||||
public static var flixyLeading: ToolbarItemPlacement {
|
||||
public static var haloLeading: ToolbarItemPlacement {
|
||||
#if os(iOS) || os(tvOS)
|
||||
.topBarLeading
|
||||
#else
|
||||
+6
-6
@@ -78,7 +78,7 @@ public enum DownloadRefusal: Error, Equatable, Sendable {
|
||||
public final class DownloadCoordinator {
|
||||
/// Die Kennung der Hintergrundsitzung. Muss über App-Starts hinweg gleich
|
||||
/// bleiben, sonst findet iOS die laufenden Übertragungen nicht wieder.
|
||||
public static let sessionIdentifier = "com.scarriffleservices.flixy.downloads"
|
||||
public static let sessionIdentifier = "com.scarriffleservices.halo.downloads"
|
||||
|
||||
public private(set) var items: [DownloadItem] = []
|
||||
/// Wie viel die Downloads insgesamt belegen.
|
||||
@@ -134,11 +134,11 @@ public final class DownloadCoordinator {
|
||||
}
|
||||
|
||||
private enum Key {
|
||||
static let wifiOnly = "flixy.downloads.wifiOnly"
|
||||
static let storageLimit = "flixy.downloads.storageLimit"
|
||||
static let deleteAfter = "flixy.downloads.deleteWatchedAfter"
|
||||
static let keepUnwatched = "flixy.downloads.keepUnwatched"
|
||||
static let maximumConcurrent = "flixy.downloads.maximumConcurrent"
|
||||
static let wifiOnly = "halo.downloads.wifiOnly"
|
||||
static let storageLimit = "halo.downloads.storageLimit"
|
||||
static let deleteAfter = "halo.downloads.deleteWatchedAfter"
|
||||
static let keepUnwatched = "halo.downloads.keepUnwatched"
|
||||
static let maximumConcurrent = "halo.downloads.maximumConcurrent"
|
||||
}
|
||||
|
||||
/// Der Container, nicht nur der Kontext.
|
||||
+1
-1
@@ -11,7 +11,7 @@ import ActivityKit
|
||||
|
||||
/// Was im Sperrbildschirm und in der Dynamic Island steht.
|
||||
///
|
||||
/// Liegt in FlixyDownloads, weil App und Widget-Erweiterung denselben Typ
|
||||
/// Liegt in HaloDownloads, weil App und Widget-Erweiterung denselben Typ
|
||||
/// brauchen — zwei getrennte Kopien wären die klassische Quelle für eine Live
|
||||
/// Activity, die sich nicht mehr aktualisieren lässt: ActivityKit gleicht die
|
||||
/// Typen über ihren vollen Namen ab.
|
||||
+1
-1
@@ -29,7 +29,7 @@ public final class NetworkConditions {
|
||||
isConstrained = path.isConstrained
|
||||
}
|
||||
}
|
||||
monitor.start(queue: DispatchQueue(label: "flixy.network"))
|
||||
monitor.start(queue: DispatchQueue(label: "halo.network"))
|
||||
}
|
||||
|
||||
deinit {
|
||||
+1
-1
@@ -70,7 +70,7 @@ public actor LocalStreamServer {
|
||||
}
|
||||
|
||||
private let cache: StreamCache
|
||||
private let logger = Logger(subsystem: "app.flixy", category: "LocalStreamServer")
|
||||
private let logger = Logger(subsystem: "app.halo", category: "LocalStreamServer")
|
||||
private var listener: NWListener?
|
||||
private var port: UInt16?
|
||||
private var playlist: String?
|
||||
+2
-2
@@ -44,7 +44,7 @@ public actor StreamCache {
|
||||
private let policy: Policy
|
||||
private let session: URLSession
|
||||
private let directory: URL
|
||||
private let logger = Logger(subsystem: "app.flixy", category: "StreamCache")
|
||||
private let logger = Logger(subsystem: "app.halo", category: "StreamCache")
|
||||
|
||||
/// Woher die Schnipsel wirklich kommen.
|
||||
private var upstream: URL?
|
||||
@@ -83,7 +83,7 @@ public actor StreamCache {
|
||||
self.session = URLSession(configuration: configuration)
|
||||
|
||||
self.directory = FileManager.default.temporaryDirectory
|
||||
.appending(path: "flixy-stream-\(UUID().uuidString)", directoryHint: .isDirectory)
|
||||
.appending(path: "halo-stream-\(UUID().uuidString)", directoryHint: .isDirectory)
|
||||
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
+4
-4
@@ -172,9 +172,9 @@ func jellyfinLeiter(host: String) async {
|
||||
}
|
||||
|
||||
let identity = JellyfinIdentity(
|
||||
client: "Flixy-Probe",
|
||||
client: "Halo-Probe",
|
||||
device: "Mac",
|
||||
deviceID: "flixy-probe-" + UUID().uuidString,
|
||||
deviceID: "halo-probe-" + UUID().uuidString,
|
||||
version: "1.0"
|
||||
)
|
||||
let client = JellyfinClient(baseURL: basis, identity: identity)
|
||||
@@ -471,9 +471,9 @@ func zeigeSpuren(host: String, suche: String) async {
|
||||
guard let passwort = String(validatingCString: getpass("Passwort: ")), !passwort.isEmpty else { return }
|
||||
|
||||
let identity = JellyfinIdentity(
|
||||
client: "Flixy-Probe",
|
||||
client: "Halo-Probe",
|
||||
device: "Mac",
|
||||
deviceID: "flixy-probe-" + UUID().uuidString,
|
||||
deviceID: "halo-probe-" + UUID().uuidString,
|
||||
version: "1.0"
|
||||
)
|
||||
let client = JellyfinClient(baseURL: basis, identity: identity)
|
||||
+1
-1
@@ -46,7 +46,7 @@ if let index = CommandLine.arguments.firstIndex(of: "--jellyfin"),
|
||||
// Kein Modus erkannt. Ohne diesen Hinweis endete das Werkzeug wortlos — und
|
||||
// ein Tippfehler im Argument sähe aus wie ein Server, der nichts liefert.
|
||||
print("""
|
||||
FlixyProbe — fragt Server über reines HTTP, ohne Oberfläche und ohne Gerät.
|
||||
HaloProbe — fragt Server über reines HTTP, ohne Oberfläche und ohne Gerät.
|
||||
|
||||
--playlist <url> Eine HLS-Leiter prüfen: Varianten, Tonspuren,
|
||||
erster Schnipsel
|
||||
+1
-1
@@ -54,7 +54,7 @@ public struct QuickConnectRequest: Sendable, Equatable, Decodable {
|
||||
///
|
||||
/// **Quick Connect** ist der erste: die App zeigt einen sechsstelligen Code,
|
||||
/// den man in einem bereits angemeldeten Client bestätigt. Das Passwort
|
||||
/// erreicht Flixy dabei nie — dieselbe Zusage, die bei Plex gilt, wo die
|
||||
/// erreicht Halo dabei nie — dieselbe Zusage, die bei Plex gilt, wo die
|
||||
/// Anmeldemaske von Plex selbst kommt.
|
||||
///
|
||||
/// **Benutzername und Passwort** sind der Rückfall, falls der Server Quick
|
||||
+1
-1
@@ -453,7 +453,7 @@ public func isInLocalNetwork() async -> Bool {
|
||||
private static func transcodeSession(itemID: String, deviceID: String) -> [URLQueryItem] {
|
||||
[
|
||||
URLQueryItem(name: "deviceId", value: deviceID),
|
||||
URLQueryItem(name: "playSessionId", value: "flixy-\(UUID().uuidString)"),
|
||||
URLQueryItem(name: "playSessionId", value: "halo-\(UUID().uuidString)"),
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import UIKit
|
||||
#endif
|
||||
|
||||
extension JellyfinIdentity {
|
||||
private static let storageKey = "flixy.jellyfin.deviceID"
|
||||
private static let storageKey = "halo.jellyfin.deviceID"
|
||||
|
||||
/// Baut die Kennung für das laufende Gerät und merkt sie sich.
|
||||
///
|
||||
+1
-1
@@ -47,7 +47,7 @@ public actor LibraryCache {
|
||||
}
|
||||
|
||||
private let root: URL
|
||||
private let logger = Logger(subsystem: "app.flixy", category: "LibraryCache")
|
||||
private let logger = Logger(subsystem: "app.halo", category: "LibraryCache")
|
||||
|
||||
/// Wie viele Titel höchstens gespeichert werden.
|
||||
///
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import FlixyDesign
|
||||
@testable import HaloDesign
|
||||
|
||||
@Suite("Kontrastkorrektur")
|
||||
struct ContrastTests {
|
||||
private let canvas = FlixyColor.canvas
|
||||
private let canvas = HaloColor.canvas
|
||||
|
||||
@Test("Schwarz zu Weiß ergibt das Maximum von 21:1")
|
||||
func extremesProduceMaximumRatio() {
|
||||
@@ -73,7 +73,7 @@ struct AccentPaletteTests {
|
||||
// hier durch, gehört sie nicht in die Palette — nicht in die
|
||||
// Korrektur.
|
||||
for option in AccentOption.palette {
|
||||
let ratio = Contrast.ratio(option.color, FlixyColor.canvas)
|
||||
let ratio = Contrast.ratio(option.color, HaloColor.canvas)
|
||||
#expect(
|
||||
ratio >= Contrast.minimumForText,
|
||||
"\(option.id) erreicht nur \(String(format: "%.2f", ratio)):1"
|
||||
@@ -146,7 +146,7 @@ struct DefaultAccentTests {
|
||||
|
||||
@Test("Die Voreinstellung braucht keine Kontrastkorrektur")
|
||||
func defaultNeedsNoCorrection() {
|
||||
let corrected = Contrast.legible(AccentOption.default.color, on: FlixyColor.canvas)
|
||||
let corrected = Contrast.legible(AccentOption.default.color, on: HaloColor.canvas)
|
||||
#expect(corrected == AccentOption.default.color)
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ struct DefaultAccentTests {
|
||||
@Suite("Gespeicherte Akzentfarbe")
|
||||
struct AccentStorageTests {
|
||||
private func freshDefaults() -> UserDefaults {
|
||||
UserDefaults(suiteName: "flixy.accent.tests.\(UUID().uuidString)")!
|
||||
UserDefaults(suiteName: "halo.accent.tests.\(UUID().uuidString)")!
|
||||
}
|
||||
|
||||
@Test("Ohne gespeicherte Auswahl gilt die Voreinstellung")
|
||||
@@ -200,7 +200,7 @@ struct AccentStorageTests {
|
||||
let defaults = freshDefaults()
|
||||
AccentOption.custom(RGB(hex: 0x8B5CF6)).store(in: defaults)
|
||||
|
||||
let theme = FlixyTheme(defaults: defaults)
|
||||
let theme = HaloTheme(defaults: defaults)
|
||||
#expect(theme.accentColor == AccentOption.storedAccentColor(in: defaults))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import FlixyDesign
|
||||
@testable import HaloDesign
|
||||
|
||||
/// Die Geometrie des Programmrasters.
|
||||
@Suite("Raster-Geometrie")
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import Testing
|
||||
@testable import FlixyDesign
|
||||
@testable import HaloDesign
|
||||
|
||||
@Suite("Auf welchem Gerät wir laufen")
|
||||
struct FlixyPlatformTests {
|
||||
struct HaloPlatformTests {
|
||||
|
||||
/// **Der Test, den es vorher nicht gab — und der gefehlt hat.**
|
||||
///
|
||||
@@ -17,6 +17,6 @@ struct FlixyPlatformTests {
|
||||
/// hält er wenigstens fest, dass die Frage gestellt gehört.
|
||||
@Test("Ein Gerät ist nicht zugleich Mac und Fernseher")
|
||||
func neverBoth() {
|
||||
#expect(!(FlixyPlatform.isMac && FlixyPlatform.isTV))
|
||||
#expect(!(HaloPlatform.isMac && HaloPlatform.isTV))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import FlixyDesign
|
||||
@testable import HaloDesign
|
||||
|
||||
/// Wo die schwebende Videokachel landet.
|
||||
@Suite("Kachel-Platzierung")
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import Testing
|
||||
@testable import FlixyDownloads
|
||||
@testable import HaloDownloads
|
||||
|
||||
/// Baut einen Koordinator, der nichts überträgt.
|
||||
///
|
||||
@@ -16,7 +16,7 @@ private func makeCoordinator(
|
||||
for: DownloadItem.self,
|
||||
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
|
||||
)
|
||||
let defaults = UserDefaults(suiteName: "flixy.tests.\(UUID().uuidString)")!
|
||||
let defaults = UserDefaults(suiteName: "halo.tests.\(UUID().uuidString)")!
|
||||
|
||||
let coordinator = DownloadCoordinator(
|
||||
container: container,
|
||||
@@ -48,7 +48,7 @@ private func plan(
|
||||
}
|
||||
|
||||
private func temporaryRoot() -> URL {
|
||||
FileManager.default.temporaryDirectory.appending(path: "flixy-tests-\(UUID().uuidString)")
|
||||
FileManager.default.temporaryDirectory.appending(path: "halo-tests-\(UUID().uuidString)")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import FlixyDownloads
|
||||
@testable import HaloDownloads
|
||||
|
||||
@Suite("Downloadrate und Restzeit")
|
||||
struct DownloadRateTests {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import MediaKit
|
||||
import Testing
|
||||
@testable import FlixyDownloads
|
||||
@testable import HaloDownloads
|
||||
|
||||
/// Baut einen Eintrag im gewünschten Zustand, ohne Datenbank.
|
||||
@MainActor
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import FlixyDownloads
|
||||
@testable import HaloDownloads
|
||||
|
||||
@Suite("Download-Zustandsautomat")
|
||||
struct DownloadStateTests {
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import FlixyDownloads
|
||||
@testable import HaloDownloads
|
||||
|
||||
/// Warum gerade nichts übertragen wird.
|
||||
@Suite("Übertragungssperre")
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
import MediaKit
|
||||
@testable import FlixyPlayback
|
||||
@testable import HaloPlayback
|
||||
|
||||
@Suite("Sprachkürzel")
|
||||
struct LanguageCodeTests {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import FlixyPlayback
|
||||
@testable import HaloPlayback
|
||||
|
||||
@Suite("Wann der teure Weg erlaubt ist")
|
||||
struct DowngradeRuleTests {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import Network
|
||||
import Testing
|
||||
@testable import FlixyPlayback
|
||||
@testable import HaloPlayback
|
||||
|
||||
/// Was der lokale Server dem Player wirklich schickt.
|
||||
///
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import FlixyPlayback
|
||||
@testable import HaloPlayback
|
||||
|
||||
@Suite("Abspielliste lesen und umschreiben")
|
||||
struct MediaPlaylistTests {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user