Skip to content

Commit

Permalink
Add Timed Walk Test & Improve Rendering Possibilities for the Questio…
Browse files Browse the repository at this point in the history
…nnaireView (#16)

# Add Timed Walk Test & Improve Rendering Possibilities for the
`QuestionnaireView`

## ⚙️ Release Notes 
- Adds the `TimedWalkTestView`
- Adds the cancellation configuration to the `QuestionnaireView` 

## 📚 Documentation
- Adds documentation for all code changes.

## ✅ Testing
- Adds automated tests for the timed walk test.

## 📝 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).

---------

Co-authored-by: Paul Schmiedmayer <[email protected]>
  • Loading branch information
dguo8412 and PSchmiedmayer authored Mar 10, 2024
1 parent f25580e commit f9d9b6d
Show file tree
Hide file tree
Showing 41 changed files with 1,227 additions and 21 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
name: Build and Test Swift Package
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: SpeziQuestionnaire.xcresult
artifactname: SpeziQuestionnaire-Package.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziQuestionnaire
scheme: SpeziQuestionnaire-Package
buildandtestuitests:
name: Build and Test UI Tests
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand All @@ -36,4 +36,4 @@ jobs:
needs: [buildandtest, buildandtestuitests]
uses: StanfordSpezi/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: SpeziQuestionnaire.xcresult TestApp.xcresult
coveragereports: SpeziQuestionnaire-Package.xcresult TestApp.xcresult
1 change: 1 addition & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ builder:
- platform: ios
documentation_targets:
- SpeziQuestionnaire
- SpeziTimedWalkTest
15 changes: 13 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import PackageDescription

let package = Package(
name: "SpeziQuestionnaire",
defaultLocalization: "en",
platforms: [
.iOS(.v17)
],
products: [
.library(name: "SpeziQuestionnaire", targets: ["SpeziQuestionnaire"])
.library(name: "SpeziQuestionnaire", targets: ["SpeziQuestionnaire"]),
.library(name: "SpeziTimedWalkTest", targets: ["SpeziTimedWalkTest"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.0.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews", from: "1.0.0"),
.package(url: "https://github.com/apple/FHIRModels", .upToNextMinor(from: "0.5.0")),
.package(url: "https://github.com/StanfordBDHG/ResearchKit", from: "2.2.25"),
.package(url: "https://github.com/StanfordBDHG/ResearchKit", from: "2.2.28"),
.package(url: "https://github.com/StanfordBDHG/ResearchKitOnFHIR", from: "1.1.0")
],
targets: [
Expand All @@ -42,6 +45,14 @@ let package = Package(
dependencies: [
.target(name: "SpeziQuestionnaire")
]
),
.target(
name: "SpeziTimedWalkTest",
dependencies: [
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziViews", package: "SpeziViews"),
.product(name: "ModelsR4", package: "FHIRModels")
]
)
]
)
12 changes: 8 additions & 4 deletions Sources/SpeziQuestionnaire/QuestionnaireView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import SwiftUI
/// ```swift
/// struct ExampleQuestionnaireView: View {
/// @State var displayQuestionnaire = false
///
///
///
/// var body: some View {
/// Button("Display Questionnaire") {
Expand All @@ -42,11 +42,12 @@ public struct QuestionnaireView: View {
private let questionnaire: Questionnaire
private let questionnaireResult: (QuestionnaireResult) async -> Void
private let completionStepMessage: String?
private let cancelBehavior: CancelBehavior


public var body: some View {
if let task = createTask(questionnaire: questionnaire) {
ORKOrderedTaskView(tasks: task, tintColor: .accentColor, result: handleResult)
ORKOrderedTaskView(tasks: task, tintColor: .accentColor, cancelBehavior: cancelBehavior, result: handleResult)
.ignoresSafeArea(.container, edges: .bottom)
.ignoresSafeArea(.keyboard, edges: .bottom)
.interactiveDismissDisabled()
Expand All @@ -59,22 +60,25 @@ public struct QuestionnaireView: View {
/// - Parameters:
/// - questionnaire: The `Questionnaire` that should be displayed.
/// - completionStepMessage: Optional completion message that can be appended at the end of the questionnaire.
/// - cancelBehavior: The cancel behavior of view. The default setting allows cancellation and asks for confirmation before the view is dismissed.
/// - questionnaireResult: Result closure that processes the ``QuestionnaireResult``.
public init(
questionnaire: Questionnaire,
completionStepMessage: String? = nil,
cancelBehavior: CancelBehavior = .shouldConfirmCancel,
questionnaireResult: @escaping @MainActor (QuestionnaireResult) async -> Void
) {
self.questionnaire = questionnaire
self.completionStepMessage = completionStepMessage
self.cancelBehavior = cancelBehavior
self.questionnaireResult = questionnaireResult
}



private func handleResult(_ result: TaskResult) async {
let questionnaireResult: QuestionnaireResult
switch result {
case let .completed(result):
let fhirResponse = result.fhirResponse
questionnaireResult = .completed(result.fhirResponse)
case .cancelled:
questionnaireResult = .cancelled
Expand Down
17 changes: 17 additions & 0 deletions Sources/SpeziQuestionnaire/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"sourceLanguage" : "en",
"strings" : {
"QUESTIONNAIRE_LOADING_ERROR_MESSAGE" : {
"comment" : "This is a string that is balh",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Questionnaire could not be loaded."
}
}
}
}
},
"version" : "1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
101 changes: 101 additions & 0 deletions Sources/SpeziTimedWalkTest/ObservationExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
// Based on https://github.com/StanfordBDHG/HealthKitOnFHIR/blob/main/Sources/HealthKitOnFHIR/Observation%20Extensions/Observation%2BCollections.swift
//
// SPDX-License-Identifier: MIT
//

import Foundation
import HealthKit
import ModelsR4


extension Observation {
private func appendElement<T>(_ element: T, to collection: ReferenceWritableKeyPath<Observation, [T]?>) {
// swiftlint:disable:previous discouraged_optional_collection
// Unfortunately we need to use an optional collection here as the ModelsR4 modules uses optional collections in the Observation type.

guard self[keyPath: collection] != nil else {
self[keyPath: collection] = [element]
return
}

self[keyPath: collection]?.append(element)
}

private func appendElements<T>(_ elements: [T], to collection: ReferenceWritableKeyPath<Observation, [T]?>) {
// swiftlint:disable:previous discouraged_optional_collection
// Unfortunately we need to use an optional collection here as the ModelsR4 modules uses optional collections in the Observation type.

if self[keyPath: collection] == nil {
self[keyPath: collection] = []
self[keyPath: collection]?.reserveCapacity(elements.count)
} else {
self[keyPath: collection]?.reserveCapacity((self[keyPath: collection]?.count ?? 0) + elements.count)
}

for element in elements {
appendElement(element, to: collection)
}
}


func appendIdentifier(_ identifier: Identifier) {
appendElement(identifier, to: \.identifier)
}

func appendIdentifiers(_ identifiers: [Identifier]) {
appendElements(identifiers, to: \.identifier)
}

func appendCategory(_ category: CodeableConcept) {
appendElement(category, to: \.category)
}

func appendCategories(_ categories: [CodeableConcept]) {
appendElements(categories, to: \.category)
}

func appendCoding(_ coding: Coding) {
appendElement(coding, to: \.code.coding)
}

func appendCodings(_ codings: [Coding]) {
appendElements(codings, to: \.code.coding)
}

func appendComponent(_ component: ObservationComponent) {
appendElement(component, to: \.component)
}

func appendComponents(_ components: [ObservationComponent]) {
appendElements(components, to: \.component)
}

func setEffective(startDate: Date, endDate: Date) {
if startDate == endDate {
effective = .dateTime(FHIRPrimitive(try? DateTime(date: startDate)))
} else {
effective = .period(
Period(
end: FHIRPrimitive(try? DateTime(date: endDate)),
start: FHIRPrimitive(try? DateTime(date: startDate))
)
)
}
}

func setIssued(on date: Date) {
issued = FHIRPrimitive(try? Instant(date: date))
}

func setValue(_ quantity: Quantity) {
value = .quantity(quantity)
}

func setValue(_ string: String) {
value = .string(string.asFHIRStringPrimitive())
}
}
99 changes: 99 additions & 0 deletions Sources/SpeziTimedWalkTest/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"sourceLanguage" : "en",
"strings" : {
"%lld" : {

},
"%lld m" : {

},
"Cancel" : {

},
"Cancel Timed Walk Test?" : {

},
"Cancel Walk Test" : {

},
"Distance:" : {

},
"Done" : {

},
"Invalid Data Error" : {

},
"Make yourself ready for the %@ minute walk test" : {

},
"Next" : {

},
"Pedometer access is not authorized" : {

},
"Pedometer data is invalid" : {

},
"Please go to the Settings App to authorize pedometer access for this application." : {

},
"Restart" : {

},
"Return" : {

},
"Start" : {

},
"Steps:" : {

},
"The %@ minute walk test will start in %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "The %1$@ minute walk test will start in %2$@"
}
}
}
},
"Timed Walk Test" : {

},
"Unauthorized Error" : {

},
"Unknown" : {

},
"Unknown Error" : {

},
"WALK_TEST_DEFAULT_COMPLETION_MESSAGE" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Great job completing the timed walk test. Please review your results and press \"Done\" to save your results."
}
}
}
},
"WALK_TEST_DEFAULT_TASK_DESCRIPTION %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Welcome to the timed minute walk test!\n\nPlease be sure that you have an enough space and time to walk for %@."
}
}
}
}
},
"version" : "1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Stanford Spezi open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Loading

0 comments on commit f9d9b6d

Please sign in to comment.