Files
Flaro/Packages/FlaroKit/Sources/FlaroDesign/PlatformShims.swift
T
ScarriffleandClaude Opus 5 a7cf6c5def Halo heisst ab hier Flaro — die Marke stand im Weg
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>
2026-10-03 19:38:27 +02:00

206 lines
7.5 KiB
Swift

import SwiftUI
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
/// Was auf iPhone, Mac und Fernseher verschieden heißt — an einer Stelle.
///
/// Nicht jede Ansicht soll `#if os(iOS)` tragen. Ein Formular, das auf beiden
/// Plattformen dasselbe meint, soll auf beiden gleich aussehen; wo die
/// Systeme verschiedene Namen dafür haben, steht der Unterschied hier und
/// 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 FlaroPlatform {
/// Läuft die App auf einem Mac?
///
/// **Zwei Wege führen dorthin, und für den Übersetzer sehen sie
/// verschieden aus.** Catalyst ist ein eigener Bau und meldet sich als
/// `targetEnvironment(macCatalyst)`. „Für iPad angepasst" ist dagegen
/// buchstäblich der iOS-Bau, der auf Apple Silicon startet — dort ist
/// dieselbe Abfrage schlicht falsch.
///
/// Wer nur die erste Hälfte prüft, schreibt Code für den Mac, der auf dem
/// Mac nicht läuft: übersetzt sauber, ist aber genau dort abgeschaltet,
/// wo er gebraucht wird. Deshalb hier beide Wege, an einer Stelle.
///
/// **Und warum unten `#error` steht.** Der `#else`-Zweig lautete `true` —
/// eine Antwort auf eine Frage, die niemand gestellt hatte. Auf tvOS fiel
/// er deshalb hindurch und meldete einen Mac: kein Übersetzungsfehler,
/// keine Warnung, nur ein Fernseher, der sich für einen Mac hielt. Eine
/// Vorgabe ist an dieser Stelle immer falsch, weil die nächste Plattform
/// nicht danach fragt, ob sie gemeint war. Jetzt bricht der Bau ab und
/// verlangt eine Entscheidung.
public static let isMac: Bool = {
#if os(tvOS)
false
#elseif targetEnvironment(macCatalyst)
true
#elseif os(iOS)
ProcessInfo.processInfo.isiOSAppOnMac
#elseif os(macOS)
true
#else
#error("FlaroPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
#endif
}()
/// Läuft die App auf einem Fernseher?
///
/// Daneben und nicht in einem Aufzählungstyp: über die Frage wird
/// nirgends verzweigt, sie steht überall in einem Fragezeichen-Ausdruck.
/// Ein Aufzählungstyp wäre Zeremonie — und „iPhone oder iPad" liesse sich
/// darin ohnehin nicht beantworten, weil das `UIDevice.current` verlangt
/// und damit den Hauptakteur.
public static let isTV: Bool = {
#if os(tvOS)
true
#elseif os(iOS) || os(macOS)
false
#else
#error("FlaroPlatform kennt diese Plattform nicht. Hier eintragen — nicht raten.")
#endif
}()
}
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 flaroInlineTitle() -> some View {
#if os(iOS)
navigationBarTitleDisplayMode(.inline)
#else
self
#endif
}
/// Ein Sheet, das die halbe Höhe einnimmt.
///
/// Auf dem Mac ist ein Sheet ein Fenster: dort zählt keine Rastung,
/// sondern eine vernünftige Mindestgröße.
public func flaroMediumSheet() -> 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
// nichts. Und eine Zeile, die nichts bewirkt, liest sich beim nächsten
// Mal wie eine, die etwas bewirkt.
self
#else
// **Zur Laufzeit, nicht beim Übersetzen.** Der `#else`-Zweig für
// 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(FlaroPlatform.isMac ? [.large] : [.medium])
#endif
}
/// Dasselbe, aber auf Wunsch bis zur vollen Höhe aufziehbar.
public func flaroResizableSheet() -> 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(FlaroPlatform.isMac ? [.large] : [.medium, .large])
#endif
}
/// Ein Formular, das auf beiden Seiten gleich aussieht.
///
/// Auf tvOS gibt es `formStyle` nicht und es ist auch nicht nötig — dort
/// fällt es in denselben Zweig wie iOS.
///
/// Auf dem Mac setzt SwiftUI sonst den Spalten-Stil: Beschriftungen rechts
/// ausgerichtet, keine abgesetzten Abschnitte, alles eng. Der gruppierte
/// 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 flaroForm() -> some View {
#if os(macOS)
formStyle(.grouped)
#else
self
#endif
}
/// Eingabefelder für Adressen und Namen: keine Großschreibung, keine
/// Korrektur — auf dem Mac gibt es beides ohnehin nicht als Tastatur.
///
/// Auf tvOS ebenfalls der `#else`-Zweig: die Bildschirmtastatur dort
/// kennt weder `keyboardType` noch Autokorrektur in dieser Form.
public func flaroPlainEntry(isAddress: Bool = false) -> some View {
#if os(iOS)
self
.keyboardType(isAddress ? .URL : .default)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
#else
autocorrectionDisabled()
#endif
}
}
extension View {
/// Keine eigene Fläche hinter der Titelzeile.
///
/// Auf der Startseite liegt darunter der Aufmacher — eine weichgezeichnete
/// 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 flaroClearToolbarBackground() -> some View {
#if os(iOS)
self
.toolbarBackground(.hidden, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
#else
self
#endif
}
}
/// Etwas in die Zwischenablage legen.
///
/// UIKit und AppKit heißen verschieden und meinen dasselbe; die Diagnose soll
/// deswegen nicht mit `#if` beginnen.
public enum FlaroClipboard {
public static func copy(_ text: String) {
#if os(tvOS)
// Es gibt keine. `UIPasteboard` ist auf tvOS als nicht verfügbar
// markiert — und ein Fernseher hat auch nichts, wohin man etwas
// einfügen könnte.
//
// **Die Reihenfolge ist der ganze Punkt.** `canImport(UIKit)` ist auf
// tvOS wahr; stünde dieser Zweig darunter, wäre es kein stiller
// Fehler, sondern ein lauter.
_ = text
#elseif canImport(UIKit)
UIPasteboard.general.string = text
#else
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(text, forType: .string)
#endif
}
}
extension ToolbarItemPlacement {
/// Rechts in der Titelzeile.
public static var flaroTrailing: ToolbarItemPlacement {
#if os(iOS) || os(tvOS)
.topBarTrailing
#else
.primaryAction
#endif
}
/// Links in der Titelzeile.
public static var flaroLeading: ToolbarItemPlacement {
#if os(iOS) || os(tvOS)
.topBarLeading
#else
.navigation
#endif
}
}