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>
73 lines
3.5 KiB
Swift
73 lines
3.5 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
|
|
/// Wo der Bildschirm aufhört — für die Ansichten, die absichtlich darüber
|
|
/// hinauslaufen.
|
|
///
|
|
/// **Warum nicht aus SwiftUI.** Der Sicherheitsrand ist in SwiftUI kein Wert,
|
|
/// den man erfragt, sondern ein Rand, den man verbraucht: wer ihn respektiert,
|
|
/// sieht ihn als null, und wer ihn mit `ignoresSafeArea` überschreitet, sieht
|
|
/// ihn danach ebenfalls als null. Ein `GeometryReader` dazwischenzuschieben
|
|
/// hilft nicht, er nimmt der Bildfläche das `ignoresSafeArea` gleich mit — und
|
|
/// dann ist das Bild seitlich um die Notch-Breite kleiner.
|
|
///
|
|
/// Der Versuch, ihn eine Ebene höher zu messen und hineinzureichen, hat es
|
|
/// auch nicht behoben: gemessen in einer Ansicht, angewendet in einer anderen,
|
|
/// über eine Drehung hinweg. Auf dem Gerät kamen dabei die Werte der falschen
|
|
/// Ausrichtung an.
|
|
///
|
|
/// **Das Fenster weiß es immer.** Es kennt seine Ausrichtung, es kennt die
|
|
/// Notch, und es antwortet in dem Moment, in dem gefragt wird. Das ist die
|
|
/// einzige Stelle in dieser App, die so fragt — und genau die eine, an der es
|
|
/// keinen SwiftUI-Weg gibt.
|
|
///
|
|
/// Gebraucht wird es für die Untertitel im Füllmodus: die Bildebene reicht
|
|
/// dort über die untere Kante hinaus, und AVFoundation zeichnet die Schrift an
|
|
/// ihren unteren Rand.
|
|
@MainActor
|
|
enum ScreenSafeArea {
|
|
/// Was der Bildschirm gerade verdeckt — Notch, Dynamic Island,
|
|
/// Home-Indikator, gerundete Ecken.
|
|
///
|
|
/// **Jedes Mal frisch gefragt, nie gemerkt.** Beim Drehen ändert sich
|
|
/// jeder dieser Werte, und ein gemerkter wäre genau dann falsch, wenn er
|
|
/// gebraucht wird.
|
|
static var current: EdgeInsets {
|
|
let i = windowInsets
|
|
// `left`/`right` und nicht `leading`/`trailing`: UIKit denkt in
|
|
// Bildschirmseiten, SwiftUI in Leserichtung. Bei einer Sprache, die
|
|
// von rechts nach links läuft, wären sie vertauscht — deshalb hier
|
|
// ausdrücklich in die Leserichtung übersetzt.
|
|
let vonLinks = fenster?.effectiveUserInterfaceLayoutDirection != .rightToLeft
|
|
return EdgeInsets(
|
|
top: i.top,
|
|
leading: vonLinks ? i.left : i.right,
|
|
bottom: i.bottom,
|
|
trailing: vonLinks ? i.right : i.left
|
|
)
|
|
}
|
|
|
|
private static var windowInsets: UIEdgeInsets { fenster?.safeAreaInsets ?? .zero }
|
|
|
|
/// Das Fenster, das gerade zählt.
|
|
///
|
|
/// **Mit Rückfall, und das ist der Punkt.** Zuerst stand hier nur
|
|
/// „die Szene, die `foregroundActive` ist" — und genau die gibt es in
|
|
/// zwei Momenten nicht: während einer Drehung und kurz nach dem
|
|
/// Zurückholen der App. Wer dann fragt, bekommt `nil`, daraus werden
|
|
/// Ränder von null, und die bleiben stehen, bis sich die Fläche wieder
|
|
/// ändert. Die Knöpfe sitzen dann im Home-Indikator, und zwar dauerhaft,
|
|
/// obwohl die Rechnung stimmt.
|
|
///
|
|
/// Ein Fenster, das die richtigen Ränder kennt, ist in dem Moment
|
|
/// trotzdem da — es ist nur nicht „aktiv". Also der Reihe nach: aktiv,
|
|
/// sonst sichtbar, sonst irgendeines.
|
|
private static var fenster: UIWindow? {
|
|
let szenen = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
let gewählt = szenen.first { $0.activationState == .foregroundActive }
|
|
?? szenen.first { $0.activationState == .foregroundInactive }
|
|
?? szenen.first
|
|
return gewählt?.keyWindow ?? gewählt?.windows.first { $0.isKeyWindow } ?? gewählt?.windows.first
|
|
}
|
|
}
|