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:
Scarriffle
2026-10-03 11:12:32 +02:00
co-authored by Claude Opus 5
parent 2fcf6821c8
commit 4b9a1e7b67
280 changed files with 1456 additions and 1456 deletions
@@ -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"]),
]
)
@@ -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)
}
}
@@ -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
}
}
@@ -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
@@ -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)
}
@@ -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()
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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)
@@ -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)
}
}
@@ -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
@@ -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)
}
}
@@ -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()
@@ -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.
@@ -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 }
@@ -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
@@ -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
@@ -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.
@@ -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.
@@ -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 {
@@ -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?
@@ -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)
}
@@ -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)
@@ -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
@@ -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
@@ -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)"),
]
}
@@ -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.
///
@@ -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.
///
@@ -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,6 +1,6 @@
import Foundation
import Testing
@testable import FlixyDesign
@testable import HaloDesign
/// Die Geometrie des Programmrasters.
@Suite("Raster-Geometrie")
@@ -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,6 +1,6 @@
import Foundation
import Testing
@testable import FlixyDesign
@testable import HaloDesign
/// Wo die schwebende Videokachel landet.
@Suite("Kachel-Platzierung")
@@ -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,5 +1,5 @@
import Testing
@testable import FlixyDownloads
@testable import HaloDownloads
@Suite("Downloadrate und Restzeit")
struct DownloadRateTests {
@@ -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,5 +1,5 @@
import Testing
@testable import FlixyDownloads
@testable import HaloDownloads
@Suite("Download-Zustandsautomat")
struct DownloadStateTests {
@@ -1,5 +1,5 @@
import Testing
@testable import FlixyDownloads
@testable import HaloDownloads
/// Warum gerade nichts übertragen wird.
@Suite("Übertragungssperre")
@@ -1,7 +1,7 @@
import Testing
import Foundation
import MediaKit
@testable import FlixyPlayback
@testable import HaloPlayback
@Suite("Sprachkürzel")
struct LanguageCodeTests {
@@ -1,6 +1,6 @@
import Testing
import Foundation
@testable import FlixyPlayback
@testable import HaloPlayback
@Suite("Wann der teure Weg erlaubt ist")
struct DowngradeRuleTests {
@@ -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,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