Widgets/notifications: tracked-event auto-advance, lock-screen dots, Snooze labels, period milestones
- Bars widget: tracked ("Track Countdown") events now also advance — a completed
or past (non-recurring) tracked event falls through to the next-nearest, and the
refresh boundary covers the tracked event's expiry. Recurring tracked events
still roll forward. Completes the KC-26 auto-advance for all paths.
- Progress Dots on the lock screen (accessoryRectangular): show 28 larger dots
instead of 100 tiny ones, render with .primary for the vibrant tint, and use
AccessoryWidgetBackground so it's legible (Home Screen keeps the 100-dot grid).
- Notifications: snooze action labels now read "Snooze 15 min/30 min/1 hour/
2 hours" (lock screen) and "Snooze 15 Minutes…" (in-app Postpone menu).
- New period milestone notifications: recurring "One more week/month/year passed"
at end of week (Sun 20:00), month (1st 09:00), and year (Jan 1 09:00); gated by
a kisani.periodMilestones flag, scheduled via the existing reschedule path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -722,10 +722,10 @@ snooze choices directly from the pressed item and notification actions.
|
||||
|
||||
### Fix (app task menus)
|
||||
- Added a shared **Postpone** submenu to `TaskMenuItems`:
|
||||
- 15 Minutes
|
||||
- 30 Minutes
|
||||
- 1 Hour
|
||||
- 2 Hours
|
||||
- Snooze 15 Minutes
|
||||
- Snooze 30 Minutes
|
||||
- Snooze 1 Hour
|
||||
- Snooze 2 Hours
|
||||
- Tomorrow
|
||||
- Wired the submenu through:
|
||||
- Today timeline + overdue/upcoming sections
|
||||
@@ -738,10 +738,10 @@ snooze choices directly from the pressed item and notification actions.
|
||||
|
||||
### Fix (lock screen notifications)
|
||||
- Task reminder notifications now include actions:
|
||||
- 15 min
|
||||
- 30 min
|
||||
- 1 hour
|
||||
- 2 hours
|
||||
- Snooze 15 min
|
||||
- Snooze 30 min
|
||||
- Snooze 1 hour
|
||||
- Snooze 2 hours
|
||||
- Notification response handler updates the stored task reminder and schedules
|
||||
a one-off snoozed notification for the selected delay.
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
|
||||
private func registerCategories() {
|
||||
let markAction = UNNotificationAction(identifier: Self.actionMarkId, title: "Mark Complete", options: [])
|
||||
let taskSnoozeActions = [
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "15", title: "15 min", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "30", title: "30 min", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "60", title: "1 hour", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "120", title: "2 hours", options: [])
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "15", title: "Snooze 15 min", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "30", title: "Snooze 30 min", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "60", title: "Snooze 1 hour", options: []),
|
||||
UNNotificationAction(identifier: Self.taskSnoozePrefix + "120", title: "Snooze 2 hours", options: [])
|
||||
]
|
||||
let taskCat = UNNotificationCategory(identifier: Self.categoryId, actions: [markAction] + taskSnoozeActions,
|
||||
intentIdentifiers: [], options: .customDismissAction)
|
||||
@@ -129,10 +129,45 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
|
||||
self.scheduleCompensations(compensations, programs: programs)
|
||||
self.scheduleTasks(snapshot: tasks)
|
||||
self.scheduleCalendarEvents(snapshot: calEvents)
|
||||
self.schedulePeriodMilestones()
|
||||
self.updateBadge(snapshot: tasks)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Period milestones ("one more week / month / year passed")
|
||||
|
||||
private static let periodIds = ["kisani.period.week", "kisani.period.month", "kisani.period.year"]
|
||||
|
||||
/// Recurring nudges at the close of each week, month, and year. Fixed identifiers
|
||||
/// + remove-then-add keep them de-duplicated across reschedules. Disabled (and
|
||||
/// cleared) when the user turns off the "Period milestones" setting.
|
||||
private func schedulePeriodMilestones() {
|
||||
center.removePendingNotificationRequests(withIdentifiers: Self.periodIds)
|
||||
guard UserDefaults.kisani.object(forKey: "kisani.periodMilestones") as? Bool ?? true else { return }
|
||||
|
||||
func add(_ id: String, _ title: String, _ body: String, _ comps: DateComponents) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = .default
|
||||
center.add(UNNotificationRequest(
|
||||
identifier: id, content: content,
|
||||
trigger: UNCalendarNotificationTrigger(dateMatching: comps, repeats: true)))
|
||||
}
|
||||
|
||||
// End of week → Sunday 20:00
|
||||
var week = DateComponents(); week.weekday = 1; week.hour = 20; week.minute = 0
|
||||
add(Self.periodIds[0], "This Week", "One more week passed.", week)
|
||||
|
||||
// End of month → 1st of the next month, 09:00
|
||||
var month = DateComponents(); month.day = 1; month.hour = 9; month.minute = 0
|
||||
add(Self.periodIds[1], "This Month", "One more month passed.", month)
|
||||
|
||||
// End of year → Jan 1, 09:00
|
||||
var year = DateComponents(); year.month = 1; year.day = 1; year.hour = 9; year.minute = 0
|
||||
add(Self.periodIds[2], "This Year", "One more year passed.", year)
|
||||
}
|
||||
|
||||
// MARK: - Compensation workout reminders (one-off, on chosen rest days)
|
||||
|
||||
private func scheduleCompensations(_ comps: [String: String], programs: [WorkoutProgram]) {
|
||||
|
||||
@@ -71,16 +71,16 @@ struct TaskMenuItems: View {
|
||||
|
||||
Menu {
|
||||
Button { onPostponeMinutes(task, 15) } label: {
|
||||
Label("15 Minutes", systemImage: "clock")
|
||||
Label("Snooze 15 Minutes", systemImage: "clock")
|
||||
}
|
||||
Button { onPostponeMinutes(task, 30) } label: {
|
||||
Label("30 Minutes", systemImage: "clock")
|
||||
Label("Snooze 30 Minutes", systemImage: "clock")
|
||||
}
|
||||
Button { onPostponeMinutes(task, 60) } label: {
|
||||
Label("1 Hour", systemImage: "clock")
|
||||
Label("Snooze 1 Hour", systemImage: "clock")
|
||||
}
|
||||
Button { onPostponeMinutes(task, 120) } label: {
|
||||
Label("2 Hours", systemImage: "clock")
|
||||
Label("Snooze 2 Hours", systemImage: "clock")
|
||||
}
|
||||
Divider()
|
||||
Button { onSetDate(task, preservingTime(on: dayOffset(1))) } label: {
|
||||
|
||||
Reference in New Issue
Block a user