Add reminder notifications to schedules
- ScheduleDocument/Schedule gain optional reminderMinutes (minutes from midnight; decode-compatible, no schema bump) - New Workout form gets a Reminder section (toggle + time picker, hidden for Now; footer warns when notifications are denied) - ReminderPlanner (pure, tested) derives notification triggers: daily repeating, per-weekday repeating for fixed days, one-shot for once - ReminderScheduler resyncs pending requests from the cache on every change, multiplexed onto onCacheChanged after the watch push; asks notification permission only once a reminder actually exists
This commit is contained in:
@@ -19,6 +19,10 @@ final class AppServices {
|
||||
let workoutLauncher = WorkoutLauncher()
|
||||
let workoutHealthDeleter = WorkoutHealthDeleter()
|
||||
|
||||
/// Keeps pending local notifications in sync with the schedules' reminder times
|
||||
/// (an idempotent full resync on every cache change).
|
||||
let reminderScheduler: ReminderScheduler
|
||||
|
||||
/// Speaks exercise instructions aloud (library Speak button + hands-free workout cues).
|
||||
/// Shared so a new exercise's cue cancels whatever the last screen was still saying.
|
||||
let speechAnnouncer = SpeechAnnouncer()
|
||||
@@ -43,6 +47,7 @@ final class AppServices {
|
||||
let liveRunState = LiveRunState()
|
||||
self.liveRunState = liveRunState
|
||||
self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine, liveRunState: liveRunState)
|
||||
self.reminderScheduler = ReminderScheduler(container: container)
|
||||
// Launch the wrist session only when a run actually begins (the first
|
||||
// exercise leaves `.notStarted` on the phone) — creating a workout alone is
|
||||
// just a peek and must not start anything on the watch. Watch-originated
|
||||
@@ -70,6 +75,16 @@ final class AppServices {
|
||||
await self.liveActivity.endStaleActivities()
|
||||
await self.syncEngine.connect()
|
||||
self.watchBridge.activate()
|
||||
// `activate()` just claimed `onCacheChanged` for the watch push — multiplex
|
||||
// rather than replace, so reminder reconciliation rides the same signal.
|
||||
let watchPush = self.syncEngine.onCacheChanged
|
||||
self.syncEngine.onCacheChanged = { [weak self] in
|
||||
watchPush?()
|
||||
self?.reminderScheduler.scheduleReconcile()
|
||||
}
|
||||
// Initial post-bootstrap resync (covers changes that happened while
|
||||
// the app wasn't running — e.g. schedules edited on another device).
|
||||
self.reminderScheduler.scheduleReconcile()
|
||||
// Past the iCloud gate: request the workout-share scope so the user can still
|
||||
// delete legacy phone-estimate workouts from Health when deleting them here.
|
||||
self.workoutHealthDeleter.authorizeIfNeeded()
|
||||
|
||||
Reference in New Issue
Block a user