Skip to content

Commit

Permalink
Implement example of notification support
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Feb 18, 2024
1 parent 6a6906b commit 9b7edc5
Show file tree
Hide file tree
Showing 22 changed files with 812 additions and 120 deletions.
33 changes: 33 additions & 0 deletions Sources/Spezi/Capabilities/ApplicationPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@
//


/// Refer to the documentation of ``Module/Application``.
@propertyWrapper
public class _ApplicationPropertyWrapper<Value> { // swiftlint:disable:this type_name
private let keyPath: KeyPath<Spezi, Value>

private weak var spezi: Spezi?
/// Some KeyPaths are declared to copy the value upon injection and not query them every time.
private var shadowCopy: Value?


/// Access the application property.
public var wrappedValue: Value {
if let shadowCopy {
return shadowCopy
}

guard let spezi else {
preconditionFailure("Underlying Spezi instance was not yet injected. @Application cannot be accessed within the initializer!")
}
return spezi[keyPath: keyPath]
}

/// Initialize a new `@Application` property wrapper
/// - Parameter keyPath: The property to access.
public init(_ keyPath: KeyPath<Spezi, Value>) {
self.keyPath = keyPath
}
Expand All @@ -30,10 +40,33 @@ public class _ApplicationPropertyWrapper<Value> { // swiftlint:disable:this type
extension _ApplicationPropertyWrapper: SpeziPropertyWrapper {
func inject(spezi: Spezi) {
self.spezi = spezi
if spezi.createsCopy(keyPath) {
self.shadowCopy = spezi[keyPath: keyPath]
}
}
}


extension Module {
/// Access a property or action of the application.
///
/// The `@Application` property wrapper can be used inside your `Module` to
/// access a property or action of your application.
///
/// - Note: You can access the contents of `@Application` once your ``Module/configure()-5pa83`` method is called
/// (e.g., it must not be used in the `init`).
///
/// Below is a short code example:
///
/// ```swift
/// class ExampleModule: Module {
/// @Application(\.logger)
/// var logger
///
/// func configure() {
/// logger.info("Module is being configured ...")
/// }
/// }
/// ```
public typealias Application<Value> = _ApplicationPropertyWrapper<Value>
}
64 changes: 32 additions & 32 deletions Sources/Spezi/Capabilities/Lifecycle/LifecycleHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public protocol LifecycleHandler {
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.willEnterForegroundNotification publisher on iOS \
Use the SwiftUI ScenePhase environment property or the UIScene.willEnterForegroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
Expand All @@ -73,10 +73,10 @@ public protocol LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.didActivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.didActivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
func sceneDidBecomeActive(_ scene: UIScene)

Expand All @@ -88,10 +88,10 @@ public protocol LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.willDeactivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.willDeactivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
func sceneWillResignActive(_ scene: UIScene)

Expand All @@ -103,10 +103,10 @@ public protocol LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.didEnterBackgroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.didEnterBackgroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
func sceneDidEnterBackground(_ scene: UIScene)

Expand Down Expand Up @@ -169,9 +169,9 @@ extension LifecycleHandler {
*,
deprecated,
message: """
Please use the new @Application property wrapper to access delegate functionality. \
Otherwise use the SwiftUI onReceive(_:perform:) for UI related notifications.
"""
Please use the new @Application property wrapper to access delegate functionality. \
Otherwise use the SwiftUI onReceive(_:perform:) for UI related notifications.
"""
)
extension Array: LifecycleHandler where Element == LifecycleHandler {
#if os(iOS) || os(visionOS) || os(tvOS)
Expand All @@ -193,10 +193,10 @@ extension Array: LifecycleHandler where Element == LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.willEnterForegroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.willEnterForegroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
public func sceneWillEnterForeground(_ scene: UIScene) {
for lifecycleHandler in self {
Expand All @@ -208,10 +208,10 @@ extension Array: LifecycleHandler where Element == LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.didActivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.didActivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
public func sceneDidBecomeActive(_ scene: UIScene) {
for lifecycleHandler in self {
Expand All @@ -223,10 +223,10 @@ extension Array: LifecycleHandler where Element == LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.willDeactivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.willDeactivateNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
public func sceneWillResignActive(_ scene: UIScene) {
for lifecycleHandler in self {
Expand All @@ -238,10 +238,10 @@ extension Array: LifecycleHandler where Element == LifecycleHandler {
*,
deprecated,
message: """
Using UISceneDelegate is deprecated. \
Use the SwiftUI onReceive(_:perform:) modifier with the UIScene.didEnterBackgroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
Using UISceneDelegate is deprecated. \
Use the SwiftUI ScenePhase environment property or the UIScene.didEnterBackgroundNotification publisher on iOS \
or other platform-specific mechanisms as a replacement.
"""
)
public func sceneDidEnterBackground(_ scene: UIScene) {
for lifecycleHandler in self {
Expand Down
81 changes: 81 additions & 0 deletions Sources/Spezi/Capabilities/Notifications/NotificationHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// 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 UserNotifications


/// Get notified about receiving notifications.
public protocol NotificationHandler {
/// Handle user-selected notification action.
///
/// This method is called with your app in the background to handle the selected user action.
///
/// For more information refer to [Handle user-selected actions](https://developer.apple.com/documentation/usernotifications/handling-notifications-and-notification-related-actions#Handle-user-selected-actions)
/// and [`userNotificationCenter(_:didReceive:withCompletionHandler:)`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/usernotificationcenter(_:didreceive:withcompletionhandler:)).
///
/// - Parameter response: The user's response to the notification.
func handleNotificationAction(_ response: UNNotificationResponse) async

/// Handle incoming notification when the app is running in foreground.
///
/// This method is called when there is a incoming notification while the app was running in foreground.
///
/// For more information refer to
/// [Handle notifications while your app runs in the foreground](https://developer.apple.com/documentation/usernotifications/handling-notifications-and-notification-related-actions#Handle-notifications-while-your-app-runs-in-the-foreground)
/// and [`userNotificationCenter(_:willPresent:withCompletionHandler:)`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/usernotificationcenter(_:willpresent:withcompletionhandler:)).
///
/// - Parameter notification: The notification that is about to be delivered.
/// - Returns: The option for notifying the user. Use `[]` to silence the notification.
func receiveIncomingNotification(_ notification: UNNotification) async -> UNNotificationPresentationOptions

#if !os(macOS)
/// Handle remote notification when the app is running in background.
///
/// This method is called when there is a remote notification arriving while the app is running in background.
/// You can use this method to download additional content.
///
/// For more information refer to
/// [`application(_:didReceiveRemoteNotification:fetchCompletionHandler:)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application)
/// or [`didReceiveRemoteNotification(_:fetchCompletionHandler:)`](https://developer.apple.com/documentation/watchkit/wkextensiondelegate/3152235-didreceiveremotenotification).
///
/// - Parameter remoteNotification: The data of the notification payload.
/// - Returns: Return the respective ``BackgroundFetchResult``.
func receiveRemoteNotification(_ remoteNotification: [AnyHashable: Any]) async -> BackgroundFetchResult
#else
/// Handle remote notification when the app is running in background.
///
/// This method is called when there is a remote notification arriving while the app is running in background.
/// You can use this method to download additional content.
///
/// For more information refer to
/// [`application(_:didReceiveRemoteNotification:)`](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428430-application).
///
/// - Parameter remoteNotification: The data of the notification payload.
func receiveRemoteNotification(_ remoteNotification: [AnyHashable: Any])
#endif
}


extension NotificationHandler {
/// Empty default implementation.
func handleNotificationAction(_ response: UNNotificationResponse) async {}

/// Empty default implementation.
func receiveIncomingNotification(_ notification: UNNotification) async -> UNNotificationPresentationOptions {
// TODO: is there are better default?

Check failure on line 70 in Sources/Spezi/Capabilities/Notifications/NotificationHandler.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (is there are better default?) (todo)
[.badge, .badge, .list, .sound] // default is to fully present the notification
}

#if !os(macOS)
func receiveRemoteNotification(_ remoteNotification: [AnyHashable: Any]) async -> BackgroundFetchResult {
.noData
}
#else
func receiveRemoteNotification(_ remoteNotification: [AnyHashable: Any]) {}
#endif
}
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
//

import Foundation


/// Get notified about device token updates for APNs.
///
/// Use this protocol when your Module needs to be notified about an updated device tokens
/// for the Apple Push Notifications service.
public protocol NotificationTokenHandler {
/// Receive an updated device token for APNs.
///
/// User this method to be notified about a changed device token for interaction
/// with the Apple Push Notifications service.
/// Use this method to send the updated token to your server-side infrastructure.
///
/// - Note: Fore more information refer to the documentation of
/// [`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application).
/// - Parameter deviceToken: The globally unique token that identifies this device to APNs.
@MainActor
func receiveUpdatedDeviceToken(_ deviceToken: Data)
}
Loading

0 comments on commit 9b7edc5

Please sign in to comment.