Skip to content

Commit

Permalink
Platform support, Application property wrapper and notifications supp…
Browse files Browse the repository at this point in the history
…ort (#98)

# Platform support, Application property wrapper and notifications
support

## ♻️ Current situation & Problem
Currently, the Core Spezi infrastructure is only developed for iOS.
However, we are aiming to support Spezi on all Apple platforms. This
step required to reengineer some Spezi infrastructure that was heavily
tailored towards the iOS platform (e.g., the `LifecycleHandler`). This
PR deprecates the `LifecycleHandler` in favor of using SwiftUI native
approaches like the
[`ScenePhase`](https://developer.apple.com/documentation/swiftui/scenephase)
environment property or platform-specific publisher-based solutions like
[`willEnterForegroundNotification`](https://developer.apple.com/documentation/uikit/uiscene/3197925-willenterforegroundnotification).
Further we introduce the new `@Application` property wrapper for
`Modules` and the `Standard` to access application-specific properties
like `launchOptions`.

Based on the new `@Application` infrastructure we resolve issues like
#80 to provide Modules with Module-specific Logger instances.

To further validate our `@Application` approach, we added infrastructure
around (Remote Push) Notifications. You can use `@Application` to
trigger application actions like `registerRemoteNotifications` or
`unregisterRemoteNotifications` and additionally adopt protocols like
`NotificationTokenHandler` and `NotificationHandler` to react to
notification actions, control how notifications are delivered in
foreground or fetch additional content for background notifications.

## ⚙️ Release Notes 
* Add support for visionOS, tvOS, watchOS and macOS.
* Deprecated the `LifecycleHandler` protocol.
* Added new `@Application` property wrapper to access application
properties and actions in a a platform-agnostic way.
* New protocol `NotificationTokenHandler` and `NotificationHandler` to
easily support notification handling within your apps.


## 📚 Documentation
There are two new articles `Interactions with Application` and `User
Notifications` that provide an overview for those new infrastructure
elements.


## ✅ Testing
_TBA_


## 📝 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
Supereg authored Feb 19, 2024
1 parent c4bf0e9 commit cbc1bb3
Show file tree
Hide file tree
Showing 46 changed files with 1,707 additions and 278 deletions.
64 changes: 56 additions & 8 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,72 @@ on:
workflow_dispatch:

jobs:
buildandtest:
name: Build and Test Swift Package
buildandtest_ios:
name: Build and Test Swift Package iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: Spezi-Package.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: Spezi-Package
buildandtestuitests:
name: Build and Test UI Tests
resultBundle: Spezi-Package-iOS.xcresult
artifactname: Spezi-Package-iOS.xcresult
buildandtest_watchos:
name: Build and Test Swift Package watchOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: Spezi-Package
destination: 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)'
resultBundle: Spezi-Package-watchOS.xcresult
artifactname: Spezi-Package-watchOS.xcresult
buildandtest_visionos:
name: Build and Test Swift Package visionOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: Spezi-Package
destination: 'platform=visionOS Simulator,name=Apple Vision Pro'
resultBundle: Spezi-Package-visionOS.xcresult
artifactname: Spezi-Package-visionOS.xcresult
buildandtest_tvos:
name: Build and Test Swift Package tvOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: Spezi-Package
destination: 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)'
resultBundle: Spezi-Package-tvOS.xcresult
artifactname: Spezi-Package-tvOS.xcresult
buildandtest_macos:
name: Build and Test Swift Package macOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: Spezi-Package
destination: 'platform=macOS,arch=arm64'
resultBundle: Spezi-Package-macOS.xcresult
artifactname: Spezi-Package-macOS.xcresult
buildandtestuitests_ios:
name: Build and Test UI Tests iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
path: Tests/UITests
scheme: TestApp
resultBundle: TestApp-iOS.xcresult
artifactname: TestApp-iOS.xcresult
buildandtestuitests_visionos:
name: Build and Test UI Tests visionOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: TestApp.xcresult
runsonlabels: '["macOS", "self-hosted"]'
path: Tests/UITests
scheme: TestApp
destination: 'platform=visionOS Simulator,name=Apple Vision Pro'
resultBundle: TestApp-visionOS.xcresult
artifactname: TestApp-visionOS.xcresult
uploadcoveragereport:
name: Upload Coverage Report
needs: [buildandtest, buildandtestuitests]
needs: [buildandtest_ios, buildandtest_watchos, buildandtest_visionos, buildandtest_tvos, buildandtest_macos, buildandtestuitests_ios, buildandtestuitests_visionos]
uses: StanfordSpezi/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: Spezi-Package.xcresult TestApp.xcresult
coveragereports: Spezi-Package-iOS.xcresult Spezi-Package-watchOS.xcresult Spezi-Package-visionOS.xcresult Spezi-Package-tvOS.xcresult Spezi-Package-macOS.xcresult TestApp-iOS.xcresult TestApp-visionOS.xcresult
3 changes: 3 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ authors:
- family-names: "Aalami"
given-names: "Oliver"
orcid: "https://orcid.org/0000-0002-7799-2429"
- family-names: "Bauer"
given-names: "Andreas"
orcid: "https://orcid.org/0000-0002-1680-237X"
title: "Spezi"
doi: 10.5281/zenodo.7538238
url: "https://github.com/StanfordSpezi/Spezi"
10 changes: 7 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ let package = Package(
name: "Spezi",
defaultLocalization: "en",
platforms: [
.iOS(.v17)
.iOS(.v17),
.visionOS(.v1),
.macOS(.v14),
.tvOS(.v17),
.watchOS(.v10)
],
products: [
.library(name: "Spezi", targets: ["Spezi"]),
.library(name: "XCTSpezi", targets: ["XCTSpezi"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "1.0.0"),
.package(url: "https://github.com/StanfordBDHG/XCTRuntimeAssertions", from: "1.0.0")
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "1.0.2"),
.package(url: "https://github.com/StanfordBDHG/XCTRuntimeAssertions", from: "1.0.1")
],
targets: [
.target(
Expand Down
72 changes: 72 additions & 0 deletions Sources/Spezi/Capabilities/ApplicationPropertyWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// 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
//


/// 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
}
}


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>
}
Loading

0 comments on commit cbc1bb3

Please sign in to comment.