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
@@ -195,12 +195,15 @@ private struct WeatherWidgetView: View {
if let lastKnown {
content(lastKnown, isStale: true)
} else {
Notice(symbol: "wifi.slash", text: "widget.weather.unavailable")
OnyxNotice(symbol: "wifi.slash",
text: String(localized: "widget.weather.unavailable", bundle: .module))
}
case .locationDenied:
Notice(symbol: "location.slash", text: "widget.weather.locationDenied")
OnyxNotice(symbol: "location.slash",
text: String(localized: "widget.weather.locationDenied", bundle: .module))
case .locationUndetermined:
Notice(symbol: "location.circle", text: "widget.weather.locationUndetermined")
OnyxNotice(symbol: "location.circle",
text: String(localized: "widget.weather.locationUndetermined", bundle: .module))
case .loading, .idle:
ProgressView()
.controlSize(.small)
@@ -217,25 +220,6 @@ private struct WeatherWidgetView: View {
WideWeather(snapshot: snapshot, isStale: isStale)
}
}
private struct Notice: View {
let symbol: String
let text: LocalizedStringKey
var body: some View {
VStack(spacing: 6) {
Image(systemName: symbol)
.font(.system(size: 15))
.foregroundStyle(Onyx.Color.textTertiary)
Text(text, bundle: .module)
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.textSecondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
private struct WideWeather: View {
let snapshot: WeatherSnapshot
let isStale: Bool
@@ -246,15 +230,15 @@ private struct WideWeather: View {
// die untere Hälfte war leer — bei Schriftgrößen von acht Punkten, die
// man auf Armlänge nicht entziffert.
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .center, spacing: 10) {
HStack(alignment: .center, spacing: Onyx.Space.row) {
Image(systemName: snapshot.condition.symbolName(isDaylight: snapshot.isDaylight))
.font(.system(size: 34))
.font(.system(size: Onyx.Symbol.hero))
.symbolRenderingMode(.hierarchical)
.foregroundStyle(Onyx.Color.textPrimary)
VStack(alignment: .leading, spacing: 0) {
Text(snapshot.temperature.onyxFormatted)
.font(.system(size: 30, weight: .medium))
.font(Onyx.Font.displayLarge)
.monospacedDigit()
.foregroundStyle(Onyx.Color.textPrimary)
if let high = snapshot.high, let low = snapshot.low {
@@ -271,7 +255,7 @@ private struct WideWeather: View {
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.textSecondary)
.lineLimit(1)
.padding(.top, 2)
.padding(.top, Onyx.Space.xxs)
if snapshot.apparentTemperature != snapshot.temperature {
Text("weather.feelsLike \(snapshot.apparentTemperature.onyxFormatted)",
@@ -283,19 +267,19 @@ private struct WideWeather: View {
Spacer(minLength: 6)
if !snapshot.hourly.isEmpty {
Divider().overlay(Onyx.Color.hairline)
OnyxHairline()
// Vier statt fünf Stunden: bei 180 Punkten Breite bleiben so
// gut vierzig je Spalte, und die Zahlen lassen sich in einer
// lesbaren Größe setzen.
HStack(spacing: 0) {
ForEach(snapshot.hourly.prefix(4)) { point in
VStack(spacing: 3) {
VStack(spacing: Onyx.Space.xs) {
Text(point.date.formatted(.dateTime.hour()))
.font(Onyx.Font.metricSmall)
.foregroundStyle(Onyx.Color.textTertiary)
Image(systemName: point.condition.symbolName(isDaylight: true))
.font(.system(size: 13))
.font(.system(size: Onyx.Symbol.medium))
.symbolRenderingMode(.hierarchical)
.foregroundStyle(Onyx.Color.textSecondary)
Text(point.temperature.onyxFormatted)
@@ -306,15 +290,15 @@ private struct WideWeather: View {
.frame(maxWidth: .infinity)
}
}
.padding(.vertical, 8)
.padding(.vertical, Onyx.Space.md)
}
Spacer(minLength: 0)
HStack(spacing: 4) {
HStack(spacing: Onyx.Space.xs) {
if isStale {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 9))
.font(.system(size: Onyx.Symbol.caption))
.foregroundStyle(Onyx.Color.warning)
}
Text(stampText)
@@ -338,10 +322,13 @@ private struct WideWeather: View {
}
}
extension Measurement where UnitType == UnitTemperature {
public extension Measurement where UnitType == UnitTemperature {
/// Ganzzahlig und in der Einheit, die zur Region des Nutzers passt.
/// Nachkommastellen bei der Außentemperatur täuschen eine Genauigkeit vor,
/// die die Vorhersage nicht hat.
///
/// Öffentlich, seit die Temperatur auch neben der Notch steht: dieselbe
/// Zahl zweimal verschieden zu runden wäre der Anfang zweier Wahrheiten.
var onyxFormatted: String {
formatted(.measurement(width: .narrow,
usage: .weather,