generated from StanfordBDHG/SwiftPackageTemplate
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for scheduling notifications (#49)
# Support for scheduling notifications ## ♻️ Current situation & Problem The newly introduced SpeziScheduler #44 didn't include support for notifications (see #45). This PR adds back this feature, providing several improvements over the previous implementation. A core challenge is that Apple limits the amount of locally scheduled notifications to 64 request at a time. Therefore, we optimize scheduling by applying the overall rules: * Schedules that can be expressed using repeating calendar-based notification triggers are scheduled that way, only ever occupying one request for all its event occurrences. * Otherwise, each event is scheduled individually using an interval-based trigger. In this case we order notification request by event occurrence. Further, we never schedule more than `30` notification at a time and never earlier than 1 month in advance to ensure other modules are still able to schedule local notifications. These settings can be adjusted by manually configuring the `SchedulerNotifications` in your `SpeziAppDelegate` * Scheduled Notifications are updated using background tasks (if necessary). We schedule a background task every week (or earlier if necessary) to update the scheduled notification requests. ## ⚙️ Release Notes * More advanced notification scheduling that tries to reduce the amount of notification request by using repeating notifications triggers (when using the `daily` and `weekly` shorthand initializes of a `Schedule`) and prioritizes event notifications by their occurrence date. * Automatically schedule events as time-sensitive notification if their duration is not `allDay`. * All Task Notifications are automatically put into the same Scheduler group. * Support notification content customization by conforming your Standard to the `SchedulerNotificationsConstraint`. * Automatically present notifications while the app is running in foreground (customize this using the `notificationPresentation` option). * Automatically request provisional notification authorization to schedule notification even without explicit authorization (customize this with the `automaticallyRequestProvisionalAuthorization` option). * A lot of other fixes and improvements. ## 📚 Documentation Added a dedicated configuration section around notifications in the documentation catalog. The documentation of the `SchedulerNotifications` module highlights the necessary steps to set up notifications for your project. ## ✅ Testing Added the `XCTSpeziScheduler` target that provide UI components to visualize scheduled notification requests. We use this in the UI tests to verify that notifications are scheduled as expected. The Test App schedules a repeating daily notification that has its first occurrence 40s after app launch. Additionally we schedule a daily repeating task that starts 1 week after initial app launch to test event-level scheduling. ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
- Loading branch information
Showing
52 changed files
with
2,479 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,4 @@ builder: | |
- platform: ios | ||
documentation_targets: | ||
- SpeziScheduler | ||
- SpeziSchedulerUI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
Sources/SpeziScheduler/Notifications/BGTaskSchedulerErrorCode+Description.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
#if canImport(BackgroundTasks) | ||
import BackgroundTasks | ||
|
||
|
||
@available(macOS, unavailable) | ||
extension BGTaskScheduler.Error.Code: @retroactive CustomStringConvertible { | ||
public var description: String { | ||
switch self { | ||
case .notPermitted: | ||
"notPermitted" | ||
case .tooManyPendingTaskRequests: | ||
"tooManyPendingTaskRequests" | ||
case .unavailable: | ||
"unavailable" | ||
@unknown default: | ||
"BGTaskSchedulerErrorCode(rawValue: \(rawValue))" | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
|
||
@usableFromInline | ||
struct BackgroundMode { | ||
@usableFromInline static let processing = BackgroundMode(rawValue: "processing") | ||
@usableFromInline static let fetch = BackgroundMode(rawValue: "fetch") | ||
|
||
@usableFromInline let rawValue: String | ||
|
||
@usableFromInline | ||
init(rawValue: String) { | ||
self.rawValue = rawValue | ||
} | ||
} | ||
|
||
|
||
extension BackgroundMode: RawRepresentable, Codable, Hashable, Sendable {} |
43 changes: 43 additions & 0 deletions
43
Sources/SpeziScheduler/Notifications/LegacyTaskModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import Foundation | ||
import UserNotifications | ||
|
||
|
||
/// Minimal model of the legacy event model to retrieve data to provide some interoperability with the legacy version. | ||
struct LegacyEventModel { | ||
let notification: UUID? | ||
} | ||
|
||
|
||
/// Minimal model of the legacy task model to retrieve data to provide some interoperability with the legacy version. | ||
struct LegacyTaskModel { | ||
let id: UUID | ||
let notifications: Bool | ||
let events: [LegacyEventModel] | ||
} | ||
|
||
|
||
extension LegacyEventModel: Decodable, Hashable, Sendable {} | ||
|
||
|
||
extension LegacyTaskModel: Decodable, Hashable, Sendable {} | ||
|
||
|
||
extension LegacyEventModel { | ||
func cancelNotification() { | ||
guard let notification else { | ||
return | ||
} | ||
|
||
let center = UNUserNotificationCenter.current() | ||
center.removeDeliveredNotifications(withIdentifiers: [notification.uuidString]) | ||
center.removePendingNotificationRequests(withIdentifiers: [notification.uuidString]) | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
Sources/SpeziScheduler/Notifications/NotificationScenePhaseScheduling.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import SwiftUI | ||
|
||
|
||
struct NotificationScenePhaseScheduling: ViewModifier { | ||
@Environment(Scheduler.self) | ||
private var scheduler: Scheduler? // modifier is injected by SchedulerNotifications and it doesn't have a direct scheduler dependency | ||
@Environment(SchedulerNotifications.self) | ||
private var schedulerNotifications | ||
|
||
@Environment(\.scenePhase) | ||
private var scenePhase | ||
|
||
nonisolated init() {} | ||
|
||
func body(content: Content) -> some View { | ||
content | ||
.onChange(of: scenePhase, initial: true) { | ||
guard let scheduler else { | ||
// by the time the modifier appears, the scheduler is injected | ||
return | ||
} | ||
|
||
switch scenePhase { | ||
case .active: | ||
_Concurrency.Task { @MainActor in | ||
await schedulerNotifications.checkForInitialScheduling(scheduler: scheduler) | ||
} | ||
case .background, .inactive: | ||
break | ||
@unknown default: | ||
break | ||
} | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
Sources/SpeziScheduler/Notifications/NotificationThread.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
|
||
/// Determine the behavior how task notifications are automatically grouped. | ||
public enum NotificationThread { | ||
/// All task notifications are put into the global SpeziScheduler notification thread. | ||
case global | ||
/// The event notification are grouped by task. | ||
case task | ||
/// Specify a custom thread identifier. | ||
case custom(String) | ||
/// No thread identifier is specified and grouping is done automatically by iOS. | ||
case none | ||
} | ||
|
||
|
||
extension NotificationThread: Sendable, Hashable, Codable {} |
37 changes: 37 additions & 0 deletions
37
Sources/SpeziScheduler/Notifications/NotificationTime.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
|
||
/// Hour, minute and second date components to determine the scheduled time of a notification. | ||
public struct NotificationTime { | ||
/// The hour component. | ||
public let hour: Int | ||
/// The minute component. | ||
public let minute: Int | ||
/// The second component | ||
public let second: Int | ||
|
||
|
||
/// Create a new notification time. | ||
/// - Parameters: | ||
/// - hour: The hour component. | ||
/// - minute: The minute component. | ||
/// - second: The second component | ||
public init(hour: Int, minute: Int = 0, second: Int = 0) { | ||
self.hour = hour | ||
self.minute = minute | ||
self.second = second | ||
|
||
precondition((0..<24).contains(hour), "hour must be between 0 and 23") | ||
precondition((0..<60).contains(minute), "minute must be between 0 and 59") | ||
precondition((0..<60).contains(second), "second must be between 0 and 59") | ||
} | ||
} | ||
|
||
|
||
extension NotificationTime: Sendable, Codable, Hashable {} |
25 changes: 25 additions & 0 deletions
25
Sources/SpeziScheduler/Notifications/PermittedBackgroundTaskIdentifier.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
|
||
@usableFromInline | ||
struct PermittedBackgroundTaskIdentifier { | ||
@usableFromInline static let speziSchedulerNotificationsScheduling = PermittedBackgroundTaskIdentifier( | ||
rawValue: "edu.stanford.spezi.scheduler.notifications-scheduling" | ||
) | ||
|
||
@usableFromInline let rawValue: String | ||
|
||
@usableFromInline | ||
init(rawValue: String) { | ||
self.rawValue = rawValue | ||
} | ||
} | ||
|
||
|
||
extension PermittedBackgroundTaskIdentifier: RawRepresentable, Hashable, Sendable, Codable {} |
Oops, something went wrong.