Files
Flaro/Flixy/Views/Settings/AppearanceView.swift
T
ScarriffleandClaude Opus 5 d54954b9c2 macOS: die App läuft jetzt auf dem Mac
Und dein Argument dafür war das beste: auf dem Mac kann ich selbst hinsehen.
Kein Simulator-Tunnel, der mitten im Prüfen abstürzt — `screencapture` auf das
Fenster der App, und zwar nur auf ihres, nicht auf den Bildschirm.

**Ein eigenes Ziel, nicht ein gemeinsames.** Die Info.plist der iOS-App ist
voller iOS-Angaben: Startbildschirm, erlaubte Ausrichtungen, Live Activities.
Die alle bedingt zu machen hieße, dass jede spätere Änderung an der iOS-Seite
über eine Stelle geht, die auch den Mac betrifft. Zwei Ziele sind ein paar
Zeilen mehr und eine Sorge weniger.

**Was den Unterschied ausmacht, steht an einer Stelle.** Ein neuer
`PlatformShims` in FlixyDesign übersetzt, was auf beiden Seiten verschieden
heißt: schmale Titelzeile, halbhohes Blatt, Eingabefelder ohne
Großschreibung, die Plätze in der Werkzeugleiste, die Zwischenablage. Zwanzig
Ansichten benutzen ihn, statt zwanzigmal `#if os(iOS)` zu tragen — sonst
wandert die Fallunterscheidung in jede neue Datei und wird beim übernächsten
Bildschirm vergessen.

Wirklich verschieden sind nur fünf Stellen: die Bildfläche (`UIView` gegen
`NSView`), der AirPlay-Knopf, die Ausrichtungssperre (auf dem Mac gibt es
nichts zu sperren), der App-Delegat für Hintergrund-Downloads und das
Anmeldefenster, das ein anderes Fenster als Anker braucht.

**Das Karussell blättert jetzt mit einem einrastenden Streifen** statt mit
`TabView(.page)` — den Seitenstil gibt es auf dem Mac nicht. Das ist nebenbei
die bessere Lösung: das verschachtelte TabView hatte die Eigenschaft, beim
Wechsel kurz zwei Seiten gleichzeitig zu zeigen.

**Was auf dem Mac fehlt und warum:** VLC. Das Binärpaket ist auf iOS
festgelegt; sein xcframework enthält zwar Mac-Teile, aber das ist eine eigene
Runde. Solange fällt die Wiedergabe dort auf den Systemplayer zurück — MKV und
DTS gehen also noch nicht. Live Activities gibt es auf dem Mac nicht, Bild-in-
Bild startet von Hand statt beim Verlassen der App.

Gesehen: die App startet, der Anmeldebildschirm steht, beide Wege sind da.
Angemeldet habe ich mich auf dem Mac **nicht** — das ginge nur, indem ich
einen Token in deinen Schlüsselbund schreibe, und dann fragt macOS dich beim
ersten Zugriff nach dem Passwort. Das machst du mit einem Klick selbst.

iOS baut unverändert, 263 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 15:45:13 +02:00

145 lines
5.3 KiB
Swift

import SwiftUI
import FlixyDesign
/// Die Farbeinstellungen.
///
/// Kuratierte Kacheln plus eine freie Farbe. Die kuratierten sind so gewählt,
/// dass sie auf dem dunklen Grund ohne Nachhilfe lesbar sind; bei einer freien
/// Farbe greift die Kontrastkorrektur — und sagt es auch, statt die Farbe
/// stillschweigend zu verändern.
struct AppearanceView: View {
@Environment(FlixyTheme.self) private var theme
@State private var customHex: String = ""
@State private var showsCustomField = false
private let columns = [GridItem(.adaptive(minimum: 64), spacing: FlixySpacing.regular)]
var body: some View {
Form {
Section {
LazyVGrid(columns: columns, spacing: FlixySpacing.regular) {
ForEach(AccentOption.palette) { option in
swatch(option)
}
}
.padding(.vertical, FlixySpacing.snug)
} header: {
Text("appearance.accent")
} footer: {
Text("appearance.accentExplanation")
}
Section {
Toggle("appearance.customColor", isOn: $showsCustomField.animation(.snappy))
if showsCustomField {
HStack {
TextField("appearance.hexPlaceholder", text: $customHex)
.flixyPlainEntry()
.onSubmit(applyCustomColor)
Button("action.apply", action: applyCustomColor)
.disabled(RGB(string: customHex) == nil)
}
if !customHex.isEmpty, RGB(string: customHex) == nil {
Text("appearance.invalidHex")
.font(.flixyCaption)
.foregroundStyle(Color.flixyDestructive)
}
}
if theme.accentWasAdjusted {
// Ohne diesen Hinweis wirkt die Korrektur wie ein Fehler:
// die gewählte Farbe sieht auf dem Bildschirm heller aus
// als im Farbwähler, und niemand wüsste warum.
Label {
Text("appearance.adjusted \(theme.accent.hexString)")
.font(.flixyCaption)
} icon: {
Image(systemName: "info.circle")
}
.foregroundStyle(Color.flixyTextSecondary)
}
}
Section("appearance.preview") {
PreviewRow()
}
}
.scrollContentBackground(.hidden)
.background(Color.flixyCanvas)
.navigationTitle(Text("appearance.title"))
.flixyInlineTitle()
.onAppear {
if theme.selection.id == AccentOption.customIdentifier {
customHex = theme.selection.color.hexString
showsCustomField = true
}
}
}
private func swatch(_ option: AccentOption) -> some View {
Button {
theme.select(option)
} label: {
Circle()
.fill(option.color.swiftUIColor)
.frame(height: 44)
.overlay {
if theme.selection == option {
Image(systemName: "checkmark")
.font(.headline)
// Dunkel auf der Farbe, nicht weiß: die Palette
// enthält helle Töne wie Amber, auf denen Weiß
// verschwinden würde.
.foregroundStyle(Color.flixyCanvas)
}
}
.overlay {
Circle()
.strokeBorder(
theme.selection == option ? Color.flixyTextPrimary : .clear,
lineWidth: 2
)
.padding(-4)
}
}
.buttonStyle(.plain)
.accessibilityLabel(Text(option.name))
.accessibilityAddTraits(theme.selection == option ? [.isButton, .isSelected] : .isButton)
}
private func applyCustomColor() {
guard let color = RGB(string: customHex) else { return }
theme.select(.custom(color))
}
}
/// Zeigt die gewählte Farbe an den Stellen, an denen sie später auftaucht.
private struct PreviewRow: View {
@Environment(\.flixyAccent) private var accent
var body: some View {
VStack(alignment: .leading, spacing: FlixySpacing.regular) {
HStack(spacing: FlixySpacing.regular) {
Label("hero.play", systemImage: "play.fill")
.font(.flixyCardTitle)
.padding(.horizontal, FlixySpacing.loose)
.padding(.vertical, FlixySpacing.snug)
.background(accent, in: .rect(cornerRadius: FlixyRadius.control))
.foregroundStyle(Color.flixyCanvas)
Label("sidebar.downloads", systemImage: "arrow.down.circle")
.font(.flixyCardTitle)
.foregroundStyle(accent)
}
WatchProgressBar(fraction: 0.45)
}
.padding(.vertical, FlixySpacing.snug)
.listRowBackground(Color.flixySurface)
}
}