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
+81
View File
@@ -0,0 +1,81 @@
#if os(iOS)
import SwiftUI
import UIKit
/// Blendet die vom System gezeichnete Seitenleiste und Tableiste aus.
///
/// Der Grund ist das Aussehen, nicht das Verhalten. `UITabBarController`
/// zeichnet seine Seitenleiste auf dem iPad als schwebende Fläche und auf dem
/// Mac angeklebt am Fensterrand — und das lässt sich nicht umstellen.
/// `preferredLayout = .overlap` ist die dafür vorgesehene Bitte; macOS verwirft
/// sie stillschweigend. Gemessen, nicht vermutet.
///
/// Statt deshalb die ganze Navigation selbst zu bauen, bleibt der
/// `TabView` **stehen** und verliert nur sein Kleid: Auswahl, getrennte
/// Navigationsstapel je Eintrag, erhaltener Scrollstand beim Wechseln,
/// Zustandspflege — all das macht weiter das System. Gezeichnet wird darüber.
///
/// Dass das geht, war kein Ratschluss: derselbe Griff hat die Leiste
/// versuchsweise schon einmal verschwinden lassen.
struct TabChromeSuppressor: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController { Probe() }
func updateUIViewController(_ controller: UIViewController, context: Context) {}
/// Ein Controller, der nichts zeigt und nur da ist, um im Baum zu stehen.
private final class Probe: UIViewController {
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
suppress()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Ein zweites Mal: beim ersten Anhängen steht der Tab-Controller
// noch nicht zwingend im Baum.
suppress()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Und bei jedem Umbau: das System stellt die Leisten wieder her,
// wenn sich die Größenklasse ändert — beim Drehen etwa.
suppress()
}
private func suppress() {
guard let controller = Self.tabBarController(from: self) else { return }
// Erst die Form festlegen, dann ausblenden — und zwar auf die
// Tableiste, nicht auf die Seitenleiste.
//
// Nur `sidebar.isHidden = true` reichte nicht: auf dem Mac stand
// die System-Seitenleiste danach wieder da, mit eigenem Pfeil und
// eigenem Knopf, hinter unserer. Solange `mode` auf `tabSidebar`
// steht, gehört die Seitenleiste zur Darstellung und das System
// stellt sie wieder her. Steht `mode` auf `tabBar`, gibt es keine
// Seitenleiste mehr, die zurückkommen könnte — und die Tableiste
// ist ausgeblendet.
if controller.mode != .tabBar { controller.mode = .tabBar }
if !controller.isTabBarHidden { controller.isTabBarHidden = true }
if !controller.sidebar.isHidden { controller.sidebar.isHidden = true }
}
/// Sucht vom eigenen Platz aus nach oben bis zur Wurzel und von dort
/// nach unten: die Leiste liegt in der SwiftUI-Sicht *über* uns, die
/// reine Elternkette führt an ihr vorbei.
private static func tabBarController(from start: UIViewController) -> UITabBarController? {
var root = start
while let parent = root.parent { root = parent }
return search(root)
}
private static func search(_ controller: UIViewController) -> UITabBarController? {
if let found = controller as? UITabBarController { return found }
for child in controller.children {
if let found = search(child) { return found }
}
return nil
}
}
}
#endif