Legt das Fundament für den Plex-Client an: - XcodeGen-Projekt nach dem Muster von Onyx (com.scarriffleservices, strict concurrency, Projektdatei nicht eingecheckt) - FlixyKit mit vier Modulen: PlexKit, FlixyDesign, FlixyPlayback, FlixyDownloads. macOS steht als Plattform mit drin, damit die Logik-Tests ohne Simulator laufen. - FlixyDesign: dunkle Farb-Tokens, Schriftskala, Poster- und Querformat-Kacheln, Aufmacher-Bild. Die Akzentfarbe fließt über die Umgebung und wird per WCAG-Rechnung lesbar gehalten. - PlexKit: Kopfzeilen-Identität, Transport-Protokoll, Fehlerarten und die Medienmodelle samt nachsichtigem Decoder. - FlixyPlayback: VideoPlayerEngine-Protokoll und PlaybackDecider — die eine Funktion, an der Phase 2 das Transcoding andockt. - Ausfahrbare Seitenleiste auf dem iPhone, Split-View auf dem iPad. - 45 Tests, alle grün. VLCKit 4.0.0-a24 ist vorab geprüft: baut über SPM unter Swift 6 mit strict concurrency. Die iOS-Slice ist mit 239 MB allerdings gewichtig, deshalb bekommt sie in Phase 1 ein eigenes Target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 lines
2.5 KiB
Swift
66 lines
2.5 KiB
Swift
import SwiftUI
|
|
|
|
/// Hält die gewählte Akzentfarbe und sorgt dafür, dass sie lesbar bleibt.
|
|
///
|
|
/// Nach außen gibt es nur `accent` — schon kontrastkorrigiert. Views sollen
|
|
/// sich nie fragen müssen, ob die eingestellte Farbe dunkel genug ist, um
|
|
/// darauf zu achten.
|
|
@MainActor
|
|
@Observable
|
|
public final class FlixyTheme {
|
|
private let defaults: UserDefaults
|
|
|
|
private enum Key {
|
|
static let selectedIdentifier = "flixy.accent.identifier"
|
|
static let customHex = "flixy.accent.customHex"
|
|
}
|
|
|
|
/// Was in den Einstellungen ausgewählt ist — unkorrigiert, damit die
|
|
/// Farbkachel genau das zeigt, was angetippt wurde.
|
|
public private(set) var selection: AccentOption
|
|
|
|
public init(defaults: UserDefaults = .standard) {
|
|
self.defaults = defaults
|
|
self.selection = Self.restore(from: defaults)
|
|
}
|
|
|
|
/// Die Farbe, die tatsächlich gezeichnet wird.
|
|
///
|
|
/// Bei den kuratierten Werten ändert die Korrektur nichts — sie erfüllen
|
|
/// den Grenzwert bereits. Sie greift nur bei eigenen Farben.
|
|
public var accent: RGB {
|
|
Contrast.legible(selection.color, on: FlixyColor.canvas)
|
|
}
|
|
|
|
public var accentColor: Color { accent.swiftUIColor }
|
|
|
|
/// Ob die Korrektur bei der aktuellen Auswahl eingegriffen hat. Der
|
|
/// Einstellungsdialog kann darauf hinweisen, statt den Nutzer rätseln zu
|
|
/// lassen, warum seine Farbe heller aussieht als gewählt.
|
|
public var accentWasAdjusted: Bool { accent != selection.color }
|
|
|
|
public func select(_ option: AccentOption) {
|
|
selection = option
|
|
defaults.set(option.id, forKey: Key.selectedIdentifier)
|
|
if option.id == AccentOption.customIdentifier {
|
|
defaults.set(option.color.hexString, forKey: Key.customHex)
|
|
}
|
|
}
|
|
|
|
private static func restore(from defaults: UserDefaults) -> AccentOption {
|
|
guard let identifier = defaults.string(forKey: Key.selectedIdentifier) else {
|
|
return .default
|
|
}
|
|
if identifier == AccentOption.customIdentifier {
|
|
// Fällt die gespeicherte Zeichenkette aus (etwa weil jemand die
|
|
// Voreinstellungen von Hand bearbeitet hat), ist der Standard
|
|
// besser als eine Farbe, die niemand gewählt hat.
|
|
guard let hex = defaults.string(forKey: Key.customHex),
|
|
let color = RGB(string: hex)
|
|
else { return .default }
|
|
return .custom(color)
|
|
}
|
|
return AccentOption.palette.first { $0.id == identifier } ?? .default
|
|
}
|
|
}
|