Kacheln und Fenster sprechen dieselbe Sprache

Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund
hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt
Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl.

Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren
deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht
mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz
auf schwarz endet.

Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das
lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient,
bis jede Kachel einmal zu sehen war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-05 21:05:51 +02:00
co-authored by Claude Opus 5
parent 9b600de9a9
commit edb327cabe
32 changed files with 3677 additions and 968 deletions
+39 -45
View File
@@ -12,7 +12,7 @@ struct FanSettingsView: View {
@Bindable var control: FanControl
var body: some View {
Form {
SettingsPage {
switch control.installState {
case .notInstalled, .failed:
installSection
@@ -23,16 +23,13 @@ struct FanSettingsView: View {
// Das Ladelimit hängt am selben Helfer wie die Lüfter — beides
// ist Hardwaresteuerung über den SMC, und beides ist erst
// erreichbar, wenn der Helfer läuft.
Section("charge.section") {
OnyxFormCard(String(localized: "charge.section")) {
ChargeLimitControl(control: control, roomy: true)
.padding(.horizontal, OnyxForm.rowPadding)
.padding(.bottom, OnyxForm.rowPadding)
}
}
}
.formStyle(.grouped)
// Der eigene Untergrund des Formulars weicht ein, zwei Prozent von dem
// des Fensters ab. In Dunkelgrau sieht man genau das als Kante.
.scrollContentBackground(.hidden)
.padding()
.onAppear {
control.refreshInstallState()
control.start()
@@ -43,37 +40,33 @@ struct FanSettingsView: View {
// MARK: - Einrichtung
private var installSection: some View {
Section {
Text("fans.intro")
.font(.callout)
.fixedSize(horizontal: false, vertical: true)
OnyxFormCard(String(localized: "fans.group.setup"),
hint: String(localized: "fans.intro")) {
// Das Risiko benennen, bevor jemand zustimmt — nicht danach.
Label("fans.warning", systemImage: "exclamationmark.triangle.fill")
.font(.callout)
.foregroundStyle(Onyx.Color.warning)
.fixedSize(horizontal: false, vertical: true)
Text("fans.safety")
.font(.callout)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
OnyxFormRow(String(localized: "fans.warning"),
hint: String(localized: "fans.safety"),
symbol: "exclamationmark.triangle.fill",
tone: .warning)
if case .failed(let message) = control.installState {
Text(message).font(.callout).foregroundStyle(Onyx.Color.critical)
OnyxFormRow(message, symbol: "xmark.octagon", tone: .critical)
}
Button("fans.install") { control.install() }
OnyxFormRow(String(localized: "fans.install")) {
Button("fans.install") { control.install() }
}
}
}
private var approvalSection: some View {
Section {
Label("fans.approval.needed", systemImage: "hand.raised")
.font(.callout)
.fixedSize(horizontal: false, vertical: true)
Button("fans.approval.open") { control.openApprovalSettings() }
Button("fans.approval.recheck") { control.refreshInstallState() }
OnyxFormCard(String(localized: "fans.group.setup")) {
OnyxFormRow(String(localized: "fans.approval.needed"),
symbol: "hand.raised") {
Button("fans.approval.open") { control.openApprovalSettings() }
}
OnyxFormRow(String(localized: "fans.action.recheck")) {
Button("fans.action.recheck") { control.refreshInstallState() }
}
}
}
@@ -82,14 +75,13 @@ struct FanSettingsView: View {
@ViewBuilder
private var controlSection: some View {
if let report = control.report {
Section {
OnyxFormCard(String(localized: "fans.group.control")) {
if let hottest = report.hottestCelsius {
HStack {
Label("fans.hottest", systemImage: "thermometer")
Spacer()
OnyxFormRow(String(localized: "fans.hottest"), symbol: "thermometer") {
Text("\(Int(hottest))°").monospacedDigit()
.foregroundStyle(hottest >= FanSafety.criticalCelsius
? Onyx.Color.critical : .secondary)
? AnyShapeStyle(Onyx.Color.critical)
: AnyShapeStyle(.secondary))
}
}
ForEach(report.fans, id: \.index) { fan in
@@ -97,22 +89,21 @@ struct FanSettingsView: View {
}
}
Section {
Text("fans.safety.active")
.font(.callout).foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
Button("fans.uninstall", role: .destructive) {
Task { await control.uninstall() }
OnyxFormCard(String(localized: "fans.group.danger"),
hint: String(localized: "fans.safety.active")) {
OnyxFormRow(String(localized: "fans.uninstall")) {
Button("settings.action.remove", role: .destructive) {
Task { await control.uninstall() }
}
}
}
} else {
Section {
HStack {
OnyxFormCard(String(localized: "fans.group.control")) {
OnyxFormRow(String(localized: "fans.connecting")) {
ProgressView().controlSize(.small)
Text("fans.connecting").foregroundStyle(.secondary)
}
if let error = control.lastError {
Text(error).font(.callout).foregroundStyle(Onyx.Color.critical)
OnyxFormRow(error, symbol: "xmark.octagon", tone: .critical)
}
}
}
@@ -127,7 +118,7 @@ private struct FanRow: View {
@State private var editing = false
var body: some View {
VStack(alignment: .leading, spacing: 6) {
VStack(alignment: .leading, spacing: Onyx.Space.sm) {
HStack {
Label(name, systemImage: "fan")
Spacer()
@@ -173,6 +164,9 @@ private struct FanRow: View {
.fixedSize(horizontal: false, vertical: true)
}
}
// Die Karte polstert nur `OnyxFormRow`; dieser Block ist ein eigener.
.padding(.horizontal, OnyxForm.rowPadding)
.padding(.vertical, Onyx.Space.row)
.onAppear { if !editing { target = max(fan.targetRPM, fan.limits.minimum) } }
.onChange(of: fan.targetRPM) { _, new in if !editing { target = new } }
}