Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging daneben. **Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler. Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim letzten Mal stattfand. --- 2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`, `Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen `FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und das Image. **Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes Ersetzen hätte den sonst zerlegt. `docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden in die Irre führt. **Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag: `com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe. 308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS bauen, das Image baut, und ein frischer Container läuft durch Einrichtung und Anmeldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
326 lines
13 KiB
YAML
326 lines
13 KiB
YAML
name: Flaro
|
|
|
|
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:
|
|
FlaroKit:
|
|
path: Packages/FlaroKit
|
|
|
|
targets:
|
|
Flaro:
|
|
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:
|
|
- FlaroUITests
|
|
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: Flaro
|
|
excludes:
|
|
- "AppIcon.icon"
|
|
- path: Flaro/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: FlaroKit
|
|
product: MediaKit
|
|
# Der Server. Steht auf der neutralen Schicht, nicht darin — kommt ein
|
|
# zweiter dazu, steht er daneben.
|
|
- package: FlaroKit
|
|
product: JellyfinKit
|
|
- package: FlaroKit
|
|
product: FlaroDesign
|
|
- package: FlaroKit
|
|
product: FlaroPlayback
|
|
- package: FlaroKit
|
|
product: FlaroDownloads
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flaro
|
|
PRODUCT_NAME: Flaro
|
|
# 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: Flaro/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Flaro
|
|
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: >-
|
|
Flaro 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: `FlaroTV/` gehört nur hierher, `Flaro/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.
|
|
FlaroTV:
|
|
type: application
|
|
platform: tvOS
|
|
# Aus demselben Grund ausgeschrieben wie bei Flaro — siehe dort.
|
|
scheme: {}
|
|
sources:
|
|
# Die eigenen Ansichten.
|
|
- path: FlaroTV
|
|
# Die Modelle.
|
|
- path: Flaro/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: Flaro/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: Flaro/Views/Player/PlayerModel.swift
|
|
type: file
|
|
- path: Flaro/Views/Player/VideoSurface.swift
|
|
type: file
|
|
- path: Flaro/Branding.swift
|
|
type: file
|
|
# Die Texte. Dieselben Schlüssel, dieselben Übersetzungen.
|
|
- path: Flaro/Resources/Localizable.xcstrings
|
|
type: file
|
|
dependencies:
|
|
- package: FlaroKit
|
|
product: MediaKit
|
|
- package: FlaroKit
|
|
product: JellyfinKit
|
|
- package: FlaroKit
|
|
product: FlaroDesign
|
|
- package: FlaroKit
|
|
product: FlaroPlayback
|
|
# Nur damit `PlayerModel` den Typ nennen darf. Angelegt wird nie einer:
|
|
# FlaroTVApp reicht `nil` durch, und damit gibt es auf tvOS auch keinen
|
|
# ModelContainer und kein SwiftData.
|
|
- package: FlaroKit
|
|
product: FlaroDownloads
|
|
- target: FlaroTopShelf
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flaro.tv
|
|
PRODUCT_NAME: Flaro
|
|
# 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 FlaroTV/Resources.
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: "App Icon & Top Shelf Image"
|
|
CODE_SIGN_STYLE: Automatic
|
|
CODE_SIGN_ENTITLEMENTS: FlaroTV/Flaro.entitlements
|
|
info:
|
|
path: FlaroTV/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Flaro
|
|
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.flaro.topshelf
|
|
CFBundleURLSchemes:
|
|
- flaro
|
|
# 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: >-
|
|
Flaro 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.
|
|
FlaroTopShelf:
|
|
type: tv-app-extension
|
|
platform: tvOS
|
|
sources:
|
|
- path: FlaroTopShelf
|
|
dependencies:
|
|
- package: FlaroKit
|
|
product: MediaKit
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flaro.tv.topshelf
|
|
PRODUCT_NAME: FlaroTopShelf
|
|
CODE_SIGN_STYLE: Automatic
|
|
CODE_SIGN_ENTITLEMENTS: FlaroTopShelf/FlaroTopShelf.entitlements
|
|
SKIP_INSTALL: YES
|
|
info:
|
|
path: FlaroTopShelf/Info.plist
|
|
properties:
|
|
CFBundleDisplayName: Flaro
|
|
# **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.
|
|
FlaroUITests:
|
|
type: bundle.ui-testing
|
|
platform: iOS
|
|
sources:
|
|
- FlaroUITests
|
|
dependencies:
|
|
- target: Flaro
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flaro.uitests
|
|
TEST_TARGET_NAME: Flaro
|
|
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: FlaroKit
|
|
product: FlaroDownloads
|
|
- package: FlaroKit
|
|
product: FlaroDesign
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.flaro.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: Flaro Downloads
|
|
NSExtension:
|
|
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|