Knopf zum Nachsehen bei Calendarr

Onyx holt sich nichts, es liest den Schnappschuss und erfährt neue über eine
Darwin-Nachricht. Wer Calendarr gerade erst gestartet hat, will aber nicht auf
den nächsten Blick warten, sondern jetzt nachsehen.

Der Knopf steht in jedem Zustand, nicht nur wenn Termine da sind: gerade wenn
„noch nichts geschrieben" oder „bitte anmelden" dasteht, will man es nach dem
Beheben sofort noch einmal versuchen. Beim Laden zeigt er einen Kreisel, in
fester Breite — sonst zuckt die Zeile, weil der Kreisel schmaler ist als der
Pfeil.

Dabei aufgefallen: die Statustexte für „noch nichts geschrieben", „abgemeldet"
und „zu neues Format" standen ohne Übersetzung im Katalog. Wer in einen dieser
Zustände geriet, las den rohen Schlüssel. Jetzt übersetzt, dazu die zwei
Zustände, die gar nicht behandelt waren — kein Kalenderzugriff und „wird
gelesen".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-14 13:23:34 +02:00
parent c8f3054036
commit 3c36092712
2 changed files with 1846 additions and 1718 deletions

View File

@@ -19,24 +19,6 @@
} }
} }
}, },
"%@ °C" : {
"comment" : "A label showing the temperature of the hottest fan in the report, in degrees Celsius. The argument is the temperature in degrees Celsius.",
"isCommentAutoGenerated" : true,
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ °C"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ °C"
}
}
}
},
"%@ U/min": { "%@ U/min": {
"comment": "A label that shows the current speed of a fan. The argument is the current speed of the fan.", "comment": "A label that shows the current speed of a fan. The argument is the current speed of the fan.",
"isCommentAutoGenerated": true, "isCommentAutoGenerated": true,
@@ -55,6 +37,24 @@
} }
} }
}, },
"%@ °C": {
"comment": "A label showing the temperature of the hottest fan in the report, in degrees Celsius. The argument is the temperature in degrees Celsius.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ °C"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ °C"
}
}
}
},
"%@°": { "%@°": {
"comment": "A temperature in °C.", "comment": "A temperature in °C.",
"isCommentAutoGenerated": true, "isCommentAutoGenerated": true,
@@ -73,16 +73,89 @@
} }
} }
}, },
"calendar.source.status.denied": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Kein Zugriff auf die Kalender."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "No access to calendars."
}
}
}
},
"calendar.source.status.incompatible": { "calendar.source.status.incompatible": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Calendarr schreibt ein neueres Format — bitte Onyx aktualisieren."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Calendarr writes a newer format — please update Onyx."
}
}
}
},
"calendar.source.status.loading": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Wird gelesen …"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Reading …"
}
}
}
}, },
"calendar.source.status.loggedOut": { "calendar.source.status.loggedOut": {
"comment": "A message that is displayed when the user is logged out of their calendar.", "comment": "A message that is displayed when the user is logged out of their calendar.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "In Calendarr anmelden."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Sign in to Calendarr."
}
}
}
}, },
"calendar.source.status.neverWritten": { "calendar.source.status.neverWritten": {
"comment": "A message that is displayed when the user has not", "comment": "A message that is displayed when the user has not",
"isCommentAutoGenerated" : true "isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Calendarr hat noch nichts geschrieben — einmal öffnen."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Calendarr hasnt written anything yet — open it once."
}
}
}
}, },
"charge.limit": { "charge.limit": {
"extractionState": "stale", "extractionState": "stale",
@@ -1547,6 +1620,22 @@
} }
} }
}, },
"options.calendar.reload": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Neu einlesen"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Reload"
}
}
}
},
"options.calendar.source": { "options.calendar.source": {
"localizations": { "localizations": {
"de": { "de": {

View File

@@ -90,7 +90,9 @@ private struct CalendarOptions: View {
} }
if model.kind != .appleCalendar { if model.kind != .appleCalendar {
CalendarrStatus(state: calendarrState) CalendarrStatus(state: calendarrState,
isLoading: model.isLoading,
onRefresh: { model.refresh() })
.task(id: model.kind) { .task(id: model.kind) {
guard model.kind != .appleCalendar else { return } guard model.kind != .appleCalendar else { return }
let now = Date() let now = Date()
@@ -103,30 +105,67 @@ private struct CalendarOptions: View {
} }
/// Was Calendarr gerade liefert. Bleibt das Widget leer, steht hier das Warum. /// Was Calendarr gerade liefert. Bleibt das Widget leer, steht hier das Warum.
///
/// Mit einem Knopf zum Nachsehen. Onyx holt sich nichts es liest den
/// Schnappschuss, den Calendarr schreibt, und erfährt neue über eine
/// Darwin-Nachricht. Die ist flüchtig: wer Calendarr gerade erst gestartet hat,
/// will nicht auf den nächsten Blick warten, sondern jetzt nachsehen.
private struct CalendarrStatus: View { private struct CalendarrStatus: View {
let state: CalendarSourceState? let state: CalendarSourceState?
let isLoading: Bool
let onRefresh: () -> Void
var body: some View { var body: some View {
switch state { HStack(spacing: 8) {
case .ready(let window): message
LabeledContent { Spacer(minLength: 8)
if case .ready(let window) = state {
Text(window.asOf?.formatted(date: .abbreviated, time: .shortened) ?? "") Text(window.asOf?.formatted(date: .abbreviated, time: .shortened) ?? "")
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} label: {
Text("options.calendar.calendarrEvents \(window.events.count)")
} }
reloadButton
}
}
@ViewBuilder
private var message: some View {
switch state {
case .ready(let window):
Text("options.calendar.calendarrEvents \(window.events.count)")
case .neverWritten: case .neverWritten:
Text("calendar.source.status.neverWritten").font(.callout).foregroundStyle(.secondary) Text("calendar.source.status.neverWritten").font(.callout).foregroundStyle(.secondary)
case .loggedOut: case .loggedOut:
Text("calendar.source.status.loggedOut").font(.callout).foregroundStyle(.secondary) Text("calendar.source.status.loggedOut").font(.callout).foregroundStyle(.secondary)
case .incompatible: case .incompatible:
Text("calendar.source.status.incompatible").font(.callout).foregroundStyle(Onyx.Color.warning) Text("calendar.source.status.incompatible").font(.callout)
.foregroundStyle(Onyx.Color.warning)
case .unreadable(let reason): case .unreadable(let reason):
Text(reason).font(.callout).foregroundStyle(Onyx.Color.warning) Text(reason).font(.callout).foregroundStyle(Onyx.Color.warning)
case .denied, .none: case .denied:
ProgressView().controlSize(.small) Text("calendar.source.status.denied").font(.callout)
.foregroundStyle(Onyx.Color.warning)
case .none:
Text("calendar.source.status.loading").font(.callout).foregroundStyle(.secondary)
} }
} }
/// Feste Breite, damit die Zeile beim Laden nicht zuckt: der Kreisel ist
/// nicht so breit wie der Pfeil.
private var reloadButton: some View {
Button(action: onRefresh) {
Group {
if isLoading {
ProgressView().controlSize(.small)
} else {
Image(systemName: "arrow.clockwise")
}
}
.frame(width: 16, height: 16)
}
.buttonStyle(.borderless)
.disabled(isLoading)
.help(Text("options.calendar.reload"))
}
} }
// MARK: - Wetter // MARK: - Wetter