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:
@@ -50,7 +50,10 @@ enum NotificationScheduler {
|
|||||||
for item in limited {
|
for item in limited {
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = item.event.title
|
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
|
content.sound = .default
|
||||||
let comps = Calendar.current.dateComponents(
|
let comps = Calendar.current.dateComponents(
|
||||||
[.year, .month, .day, .hour, .minute, .second], from: item.fire)
|
[.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 {
|
private static func bodyText(_ ev: CalEvent) -> String {
|
||||||
var parts: [String] = []
|
var parts: [String] = []
|
||||||
if !ev.isAllDay {
|
if !ev.isAllDay {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ struct SettingsView: View {
|
|||||||
Form {
|
Form {
|
||||||
profilSection
|
profilSection
|
||||||
privatsphaereSection
|
privatsphaereSection
|
||||||
|
termindauerSection
|
||||||
benachrichtigungenSection
|
benachrichtigungenSection
|
||||||
geteilterKalenderSection
|
geteilterKalenderSection
|
||||||
liquidGlassSection
|
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
|
// MARK: – Geteilter Kalender
|
||||||
|
|
||||||
var geteilterKalenderSection: some View {
|
var geteilterKalenderSection: some View {
|
||||||
@@ -361,12 +377,6 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
Toggle(L10n.t("settings.dimpast", appLang), isOn: $dimPastEvents)
|
Toggle(L10n.t("settings.dimpast", appLang), isOn: $dimPastEvents)
|
||||||
.tint(Color.accentColor)
|
.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) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user