feat: show relative time in notifications + move default duration to profile settings

Notifications now show "in 30 Min. · 10:00" instead of just the time,
giving users immediate context about how soon the event is.

Default event duration picker moved from the View section to right below
the privacy setting, grouped under the calendar/profile settings area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-06-15 19:38:20 +02:00
parent cc3d16ddce
commit 52040fff53
2 changed files with 28 additions and 7 deletions

View File

@@ -50,7 +50,10 @@ enum NotificationScheduler {
for item in limited {
let content = UNMutableNotificationContent()
content.title = item.event.title
content.body = bodyText(item.event)
let minutes = Int(item.event.startDate.timeIntervalSince(item.fire) / 60)
let rel = relativeText(minutes)
let detail = bodyText(item.event)
content.body = detail.isEmpty ? rel : "\(rel) · \(detail)"
content.sound = .default
let comps = Calendar.current.dateComponents(
[.year, .month, .day, .hour, .minute, .second], from: item.fire)
@@ -60,6 +63,14 @@ enum NotificationScheduler {
}
}
private static func relativeText(_ minutes: Int) -> String {
if minutes < 60 { return "in \(minutes) Min." }
if minutes == 60 { return "in 1 Std." }
if minutes < 1440 { return "in \(minutes / 60) Std." }
if minutes == 1440 { return "morgen" }
return "in \(minutes / 1440) Tagen"
}
private static func bodyText(_ ev: CalEvent) -> String {
var parts: [String] = []
if !ev.isAllDay {

View File

@@ -39,6 +39,7 @@ struct SettingsView: View {
Form {
profilSection
privatsphaereSection
termindauerSection
benachrichtigungenSection
geteilterKalenderSection
liquidGlassSection
@@ -147,6 +148,21 @@ struct SettingsView: View {
}
}
// MARK: Standardtermindauer
var termindauerSection: some View {
Section {
Picker(L10n.t("settings.default_duration", appLang), selection: $defaultEventDurationMinutes) {
ForEach([15, 30, 45, 60, 90, 120, 240], id: \.self) { m in
Text(ReminderOptions.durationLabel(m, appLang)).tag(m)
}
}
.onChange(of: defaultEventDurationMinutes) { _, _ in SettingsSync.push(api: api) }
} header: {
Text(L10n.t("settings.calview", appLang))
}
}
// MARK: Geteilter Kalender
var geteilterKalenderSection: some View {
@@ -361,12 +377,6 @@ struct SettingsView: View {
}
Toggle(L10n.t("settings.dimpast", appLang), isOn: $dimPastEvents)
.tint(Color.accentColor)
Picker(L10n.t("settings.default_duration", appLang), selection: $defaultEventDurationMinutes) {
ForEach([15, 30, 45, 60, 90, 120, 240], id: \.self) { m in
Text(ReminderOptions.durationLabel(m, appLang)).tag(m)
}
}
.onChange(of: defaultEventDurationMinutes) { _, _ in SettingsSync.push(api: api) }
}
}