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>
326 lines
13 KiB
YAML
326 lines
13 KiB
YAML
name: Halo
|
|
|
|
options:
|
|
bundleIdPrefix: com.scarriffleservices
|
|
deploymentTarget:
|
|
iOS: "26.0"
|
|
macOS: "26.0"
|
|
tvOS: "26.0"
|
|
createIntermediateGroups: true
|
|
groupSortPosition: top
|
|
developmentLanguage: en
|
|
|
|
settings:
|
|
base:
|
|
DEVELOPMENT_TEAM: PP34X97WS3
|
|
SWIFT_VERSION: "6.0"
|
|
MARKETING_VERSION: "0.1.0"
|
|
CURRENT_PROJECT_VERSION: "1"
|
|
SWIFT_STRICT_CONCURRENCY: complete
|
|
|
|
packages:
|
|
HaloKit:
|
|
path: Packages/HaloKit
|
|
|
|
targets:
|
|
Halo:
|
|
type: application
|
|
platform: iOS
|
|
# Ausdrücklich aufgeschrieben, statt Xcode raten zu lassen.
|
|
#
|
|
# Xcode legt für jedes Ziel von selbst ein Schema an — aber im
|
|
# Benutzerordner, und `xcodegen generate` räumt den mit weg. Danach
|
|
# findet `xcodebuild` kein Schema mehr und kann die Pakete nicht
|
|
# auflösen. Hier steht es im Projekt und überlebt jedes Neuerzeugen.
|
|
scheme:
|
|
testTargets:
|
|
- HaloUITests
|
|
sources:
|
|
# Das Icon ist ein Bündel, also technisch ein Ordner. Ohne diesen
|
|
# Ausschluss läuft XcodeGen hinein und trägt icon.json und play.svg
|
|
# einzeln ein — dann fehlt das Icon, ohne dass irgendwo ein Fehler
|
|
# auftaucht.
|
|
#
|
|
- path: Halo
|
|
excludes:
|
|
- "AppIcon.icon"
|
|
- path: Halo/AppIcon.icon
|
|
type: file
|
|
dependencies:
|
|
- target: DownloadWidget
|
|
# Die neutrale Grundschicht. Ausdrücklich genannt, obwohl sie über
|
|
# JellyfinKit ohnehin im Baum hängt: die App soll sie direkt benutzen und
|
|
# nicht davon abhängen, dass ein bestimmter Server sie mitschleppt.
|
|
- package: HaloKit
|
|
product: MediaKit
|
|
# Der Server. Steht auf der neutralen Schicht, nicht darin — kommt ein
|
|
# zweiter dazu, steht er daneben.
|
|
- package: HaloKit
|
|
product: JellyfinKit
|
|
- package: HaloKit
|
|
product: HaloDesign
|
|
- package: HaloKit
|
|
product: HaloPlayback
|
|
- package: HaloKit
|
|
product: HaloDownloads
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo
|
|
PRODUCT_NAME: Halo
|
|
# Verweist auf AppIcon.icon. Der frühere leere AppIcon.appiconset im
|
|
# Asset-Katalog ist entfernt — zwei Dinge namens AppIcon, und Xcode
|
|
# nimmt das falsche.
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
|
# iPhone und iPad aus demselben Target.
|
|
TARGETED_DEVICE_FAMILY: "1,2"
|
|
CODE_SIGN_STYLE: Automatic
|
|
info:
|
|
path: Halo/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Halo
|
|
UILaunchScreen:
|
|
UIColorName: LaunchBackground
|
|
|
|
# Der harte Schalter für „nur dunkel". Eine Ansicht mit
|
|
# .preferredColorScheme(.dark) allein reicht nicht: Systemdialoge,
|
|
# Statusleiste und Tastatur richten sich danach nicht.
|
|
UIUserInterfaceStyle: Dark
|
|
|
|
# Alle drei bleiben eingetragen: die Info.plist sagt, was die App
|
|
# grundsätzlich kann. Was davon gerade gilt, entscheidet zur Laufzeit
|
|
# OrientationLock über den App-Delegaten — Hochformat beim Blättern,
|
|
# Querformat im Player.
|
|
UISupportedInterfaceOrientations:
|
|
- UIInterfaceOrientationPortrait
|
|
- UIInterfaceOrientationLandscapeLeft
|
|
- UIInterfaceOrientationLandscapeRight
|
|
UISupportedInterfaceOrientations~ipad:
|
|
- UIInterfaceOrientationPortrait
|
|
- UIInterfaceOrientationPortraitUpsideDown
|
|
- UIInterfaceOrientationLandscapeLeft
|
|
- UIInterfaceOrientationLandscapeRight
|
|
|
|
# Ohne diesen Modus hält iOS die Wiedergabe an, sobald die App in den
|
|
# Hintergrund geht — und Bild-in-Bild gäbe es gar nicht.
|
|
UIBackgroundModes:
|
|
- audio
|
|
|
|
# Die Ausfuhr-Erklärung zur Verschlüsselung.
|
|
#
|
|
# `false` heißt: die App nutzt ausschließlich Verschlüsselung, die
|
|
# unter die Ausnahmeregelung fällt. Das trifft hier zu — HTTPS über
|
|
# URLSession und der Schlüsselbund für die Tokens, beides vom
|
|
# Betriebssystem gestellt. Eigene Algorithmen gibt es keine.
|
|
#
|
|
# Ohne diesen Eintrag fragt App Store Connect bei JEDEM Build erneut
|
|
# („Missing Compliance"), und der Build lässt sich bis dahin nicht
|
|
# verteilen.
|
|
#
|
|
# Zu beachten, falls sich das ändert: solange nur Dateien vom eigenen
|
|
# Server abgespielt werden, bleibt es bei Standardverfahren. Käme
|
|
# irgendwann etwas mit eigener Verschlüsselung dazu — DRM etwa —,
|
|
# gehört dieser Wert überprüft.
|
|
ITSAppUsesNonExemptEncryption: false
|
|
|
|
# Ohne diesen Schlüssel lehnt ActivityKit jede Anfrage ab — und
|
|
# zwar wortlos, was die Suche nach dem Grund unangenehm macht.
|
|
NSSupportsLiveActivities: true
|
|
|
|
NSAppTransportSecurity:
|
|
# Medienserver im Heimnetz laufen überwiegend auf blankem HTTP —
|
|
# Jellyfin ab Werk auf Port 8096. Diese Ausnahme erlaubt genau das,
|
|
# ohne die Transportsicherheit für das offene Netz abzuschalten.
|
|
NSAllowsLocalNetworking: true
|
|
|
|
# Seit iOS 14 verlangt jeder Zugriff auf Adressen im eigenen Netz eine
|
|
# Freigabe. Fehlt der Text, verweigert das System sie kommentarlos und
|
|
# die Serversuche findet dauerhaft nichts.
|
|
NSLocalNetworkUsageDescription: >-
|
|
Halo benötigt Zugriff auf das lokale Netzwerk, um deinen
|
|
Jellyfin-Server zu erreichen und Medien direkt von ihm zu laden.
|
|
|
|
# Die Fassung für den Fernseher.
|
|
#
|
|
# **Eigene Ansichten, geteilte Modelle.** Die Quellenpfade sind die ganze
|
|
# Aussage dieses Blocks: `HaloTV/` gehört nur hierher, `Halo/Session/`
|
|
# gehört beiden. Keine Datei muss dafür umziehen — XcodeGen nimmt Pfade je
|
|
# Ziel, und die iOS-Fassung sieht von der ganzen Sache nichts.
|
|
HaloTV:
|
|
type: application
|
|
platform: tvOS
|
|
# Aus demselben Grund ausgeschrieben wie bei Halo — siehe dort.
|
|
scheme: {}
|
|
sources:
|
|
# Die eigenen Ansichten.
|
|
- path: HaloTV
|
|
# Die Modelle.
|
|
- path: Halo/Session
|
|
excludes:
|
|
# Ein Fernseher dreht sich nicht.
|
|
- "OrientationLock.swift"
|
|
# Heruntergeladen wird auf dem Fernseher nichts.
|
|
#
|
|
# `DownloadRefiller.swift` und `DownloadService.swift` kommen
|
|
# trotzdem mit: `PlayerModel` nennt den Nachfüller in einem Zweig,
|
|
# der auf tvOS nie läuft (`downloads` ist dort `nil`), und der
|
|
# Nachfüller wiederum den Dienst. Sie auszuschliessen hiesse, eine
|
|
# Plattformfrage mitten in zweitausend Zeilen Wiedergabelogik zu
|
|
# tragen — teurer als zwei Dateien mitzuübersetzen, die niemand
|
|
# aufruft. Beide sind reine Übersetzer ohne UIKit.
|
|
# Reines Modell trotz des Ordnernamens: LoadState, ErrorText und
|
|
# MediaRoute liegen zusammen in einer Datei.
|
|
- path: Halo/Views/LoadState.swift
|
|
type: file
|
|
# Der Player. **Ohne PlayerView** — die zeichnet auf iOS das Bild und
|
|
# alle Bedienelemente selbst; auf dem Fernseher macht das Apples
|
|
# AVPlayerViewController. Das Modell darunter ist dasselbe.
|
|
- path: Halo/Views/Player/PlayerModel.swift
|
|
type: file
|
|
- path: Halo/Views/Player/VideoSurface.swift
|
|
type: file
|
|
- path: Halo/Branding.swift
|
|
type: file
|
|
# Die Texte. Dieselben Schlüssel, dieselben Übersetzungen.
|
|
- path: Halo/Resources/Localizable.xcstrings
|
|
type: file
|
|
dependencies:
|
|
- package: HaloKit
|
|
product: MediaKit
|
|
- package: HaloKit
|
|
product: JellyfinKit
|
|
- package: HaloKit
|
|
product: HaloDesign
|
|
- package: HaloKit
|
|
product: HaloPlayback
|
|
# Nur damit `PlayerModel` den Typ nennen darf. Angelegt wird nie einer:
|
|
# HaloTVApp reicht `nil` durch, und damit gibt es auf tvOS auch keinen
|
|
# ModelContainer und kein SwiftData.
|
|
- package: HaloKit
|
|
product: HaloDownloads
|
|
- target: HaloTopShelf
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.tv
|
|
PRODUCT_NAME: Halo
|
|
# Nur Apple TV.
|
|
TARGETED_DEVICE_FAMILY: "3"
|
|
# Kein AppIcon.icon: Icon Composer deckt iOS, macOS und watchOS ab,
|
|
# tvOS nicht. Der Name zeigt auf die Brand-Assets-Gruppe im Katalog,
|
|
# und der Katalog liegt unter HaloTV/Resources.
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: "App Icon & Top Shelf Image"
|
|
CODE_SIGN_STYLE: Automatic
|
|
CODE_SIGN_ENTITLEMENTS: HaloTV/Halo.entitlements
|
|
info:
|
|
path: HaloTV/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Halo
|
|
UIUserInterfaceStyle: Dark
|
|
ITSAppUsesNonExemptEncryption: false
|
|
# Über dieses Schema schickt das Regal zurück in die App. Ohne den
|
|
# Eintrag kennt tvOS es nicht, und ein Druck auf ein Plakat tut
|
|
# nichts — ohne Fehler, ohne Hinweis.
|
|
CFBundleURLTypes:
|
|
- CFBundleURLName: com.scarriffleservices.halo.topshelf
|
|
CFBundleURLSchemes:
|
|
- halo
|
|
# Was hier **nicht** steht, und warum:
|
|
#
|
|
# UILaunchScreen — tvOS hat kein Startbild dieser Art.
|
|
# UISupportedInterfaceOrientations — ein Fernseher dreht sich nicht.
|
|
# NSSupportsLiveActivities — ActivityKit fehlt im tvOS-SDK.
|
|
# CFBundleURLTypes — die Anmeldung braucht kein Rückkehr-Schema.
|
|
# UIBackgroundModes: audio — ein Apple TV schickt keine App in den
|
|
# Hintergrund, aus dem sie weiterspielen müsste.
|
|
NSAppTransportSecurity:
|
|
NSAllowsLocalNetworking: true
|
|
NSLocalNetworkUsageDescription: >-
|
|
Halo benötigt Zugriff auf das lokale Netzwerk, um deinen
|
|
Jellyfin-Server zu erreichen.
|
|
|
|
# Das Regal über der App im tvOS-Menü.
|
|
#
|
|
# Eine eigene Erweiterung, weil tvOS sie zeichnet, während die App **nicht**
|
|
# läuft — und mit weniger als einer Sekunde Zeit. Sie liest deshalb nur eine
|
|
# Datei aus der App-Gruppe, die die App beim Laden der Startseite ablegt:
|
|
# kein Netz, kein Schlüsselbund, kein Blättern.
|
|
HaloTopShelf:
|
|
type: tv-app-extension
|
|
platform: tvOS
|
|
sources:
|
|
- path: HaloTopShelf
|
|
dependencies:
|
|
- package: HaloKit
|
|
product: MediaKit
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.tv.topshelf
|
|
PRODUCT_NAME: HaloTopShelf
|
|
CODE_SIGN_STYLE: Automatic
|
|
CODE_SIGN_ENTITLEMENTS: HaloTopShelf/HaloTopShelf.entitlements
|
|
SKIP_INSTALL: YES
|
|
info:
|
|
path: HaloTopShelf/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Halo
|
|
# **Die Erweiterung hat ihre eigene Info.plist.**
|
|
#
|
|
# Und damit ihre eigene Transportsicherheit: die Ausnahme in der
|
|
# Info.plist der App gilt hier nicht. Ohne sie blockt das System jedes
|
|
# Plakat weg, das über blankes HTTP vom Jellyfin im Heimnetz kommt —
|
|
# lautlos, und das Regal bleibt leer. Genau so sieht „geht halt nicht"
|
|
# aus.
|
|
NSAppTransportSecurity:
|
|
NSAllowsLocalNetworking: true
|
|
NSExtension:
|
|
NSExtensionPointIdentifier: com.apple.tv-top-shelf
|
|
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).TopShelfProvider
|
|
|
|
# Nur zum Hinsehen: fährt die App mit der Attrappe in einen bestimmten
|
|
# Zustand und hält sie dort an, damit von außen fotografiert werden kann.
|
|
# Kein Teil der App, wird nie mitgeliefert.
|
|
HaloUITests:
|
|
type: bundle.ui-testing
|
|
platform: iOS
|
|
sources:
|
|
- HaloUITests
|
|
dependencies:
|
|
- target: Halo
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.uitests
|
|
TEST_TARGET_NAME: Halo
|
|
TARGETED_DEVICE_FAMILY: "1,2"
|
|
CODE_SIGN_STYLE: Automatic
|
|
GENERATE_INFOPLIST_FILE: YES
|
|
|
|
# Die Live Activity für Downloads. Eine eigene Erweiterung, weil iOS die
|
|
# Darstellung im Sperrbildschirm in einem getrennten Prozess zeichnet — die
|
|
# App selbst läuft zu dem Zeitpunkt meist gar nicht.
|
|
# Dieselben Quellen, dasselbe Aussehen.
|
|
DownloadWidget:
|
|
type: app-extension
|
|
platform: iOS
|
|
sources:
|
|
- path: DownloadWidget
|
|
dependencies:
|
|
- package: HaloKit
|
|
product: HaloDownloads
|
|
- package: HaloKit
|
|
product: HaloDesign
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.halo.downloadwidget
|
|
PRODUCT_NAME: DownloadWidget
|
|
CODE_SIGN_STYLE: Automatic
|
|
SKIP_INSTALL: YES
|
|
# Von XcodeGen erzeugt statt von Hand geschrieben. Eine eigene Datei ohne
|
|
# CFBundleIdentifier führt zu einer Erweiterung ganz ohne Kennung — und
|
|
# der Fehler lautet dann „not prefixed with the parent app's bundle
|
|
# identifier", was auf eine falsche Fährte führt.
|
|
info:
|
|
path: DownloadWidget/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Halo Downloads
|
|
NSExtension:
|
|
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|