Skip to content

Commit

Permalink
Added reaction test to assessment (#46)
Browse files Browse the repository at this point in the history
# Added ReactionTest to Assessment Page

## ♻️ Current situation & Problem
Contributed another assessment to the assessment page. 


## ⚙️ Release Notes 
Added ReactionTime Assessment on the Assessment Page with one attempt
and connected it to the result visualization.


## 📚 Documentation
Utilized ReactionTime from ResearchKit. 
<img width="150" alt="Screenshot 2024-03-06 at 1 53 32 pm"
src="https://github.com/CS342/2024-PICS/assets/137839789/cebb643e-a323-4f4d-a58e-5839390083bb">
<img width="150" alt="Screenshot 2024-03-06 at 1 53 19 pm"
src="https://github.com/CS342/2024-PICS/assets/137839789/fab8c26c-e6aa-44ef-b7ea-c1171ee3d163">

## ✅ Testing

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
czhang771 authored Mar 7, 2024
1 parent 77422af commit 501b616
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 6 deletions.
4 changes: 4 additions & 0 deletions PICS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
22365B782B902C9100C4528E /* Onboarding-Questionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 22365B772B902C9100C4528E /* Onboarding-Questionnaire.json */; };
226C16F02B69DF3500FBA97D /* HeightKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226C16EF2B69DF3500FBA97D /* HeightKey.swift */; };
226C16F22B6C820C00FBA97D /* WeightKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226C16F12B6C820B00FBA97D /* WeightKey.swift */; };
22C75CDA2B969B89008986AF /* ReactionTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C75CD92B969B89008986AF /* ReactionTime.swift */; };
22C75CE12B978E33008986AF /* Medication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C75CE02B978E33008986AF /* Medication.swift */; };
22C75CF02B979D02008986AF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C75CEF2B979D02008986AF /* ContentView.swift */; };
22C75CF72B979EA7008986AF /* ImageSource in Frameworks */ = {isa = PBXBuildFile; productRef = 22C75CF62B979EA7008986AF /* ImageSource */; };
Expand Down Expand Up @@ -140,6 +141,7 @@
22365B772B902C9100C4528E /* Onboarding-Questionnaire.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "Onboarding-Questionnaire.json"; sourceTree = "<group>"; };
226C16EF2B69DF3500FBA97D /* HeightKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeightKey.swift; sourceTree = "<group>"; };
226C16F12B6C820B00FBA97D /* WeightKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightKey.swift; sourceTree = "<group>"; };
22C75CD92B969B89008986AF /* ReactionTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionTime.swift; sourceTree = "<group>"; };
22C75CE02B978E33008986AF /* Medication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Medication.swift; sourceTree = "<group>"; };
22C75CEF2B979D02008986AF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -478,6 +480,7 @@
864909412B8AA1C300054C9A /* TrailMakingTest.swift */,
A459932B2B906C3B00A98C95 /* ResultsViz.swift */,
74AE05872B918DAF00AB5287 /* StroopTest.swift */,
22C75CD92B969B89008986AF /* ReactionTime.swift */,
);
path = Assessment;
sourceTree = "<group>";
Expand Down Expand Up @@ -767,6 +770,7 @@
A480C7C02B6D5A3700B29A07 /* HKVisualization.swift in Sources */,
2F5E32BD297E05EA003432F8 /* PICSDelegate.swift in Sources */,
A459932C2B906C3B00A98C95 /* ResultsViz.swift in Sources */,
22C75CDA2B969B89008986AF /* ReactionTime.swift in Sources */,
2FE5DC5229EDD7FA004B9AB4 /* PICSScheduler.swift in Sources */,
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */,
22306A462B8FB173000A8EC1 /* OnboardingQuestionnaireDashboard.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/ResearchKit",
"state" : {
"revision" : "15f06cf7c1d2d22805b7b939823536bc78ad63a6",
"version" : "2.2.25"
"revision" : "e4afb10ec636a70e06afc4a84fb98e457818439a",
"version" : "2.2.27"
}
},
{
Expand Down
51 changes: 51 additions & 0 deletions PICS/Assessment/Assessments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct Assessments: View {
enum Assessments {
case trailMaking
case stroopTest
case reactionTime
}

// Binding to control the display of account-related UI.
Expand All @@ -32,6 +33,7 @@ struct Assessments: View {
// Local storage of results of the Trail Making and Stroop tests for test results for layerplotting, analysis etc.
@AppStorage("trailMakingResults") private var tmStorageResults: [AssessmentResult] = []
@AppStorage("stroopTestResults") private var stroopTestResults: [AssessmentResult] = []
@AppStorage("reactionTimeResults") private var reactionTimeResults: [AssessmentResult] = []
// Decide whether to show test or not.
@AppStorage("AssessmentsInProgress") private var assessmentsIP = false
// Tracks which test is currently selected.
Expand All @@ -46,6 +48,8 @@ struct Assessments: View {
.padding(10)
stroopTestSection
.padding(10)
reactionTimeSection
.padding(10)
}
}

Expand All @@ -58,6 +62,8 @@ struct Assessments: View {
TrailMakingTaskView()
case .stroopTest:
StroopTestView()
case .reactionTime:
ReactionTimeView()
}
} else {
assessmentList
Expand All @@ -76,6 +82,8 @@ struct Assessments: View {
TrailMakingTaskView()
case .stroopTest:
StroopTestView()
case .reactionTime:
ReactionTimeView()
}
}
}
Expand Down Expand Up @@ -123,6 +131,28 @@ struct Assessments: View {
}
}
}
private var reactionTimeSection: some View {
// Button text to start the ReactionTime Test or view results
// based on whether results are available.
let btnText = if reactionTimeResults.isEmpty {
String(localized: "ASSESSMENT_STROOP_START_BTN")
} else {
String(localized: "ASSESSMENT_RESULTS_BTN")
}
return Section {
VStack {
reactionTimeResultsView
Divider()
.padding(.bottom, 5)
Button(action: startReactionTimeTest) {
Text(btnText)
.foregroundStyle(.accent)
}
// Use style to restrict clickable area.
.buttonStyle(.plain)
}
}
}

// Views for displaying results of Trail Making, or a message indicating the test has not been completed.
private var trailMakingTestResultsView: some View {
Expand Down Expand Up @@ -155,6 +185,21 @@ struct Assessments: View {
}
}
}
// Views for displaying results of the ReactionTime test, or a message indicating the test has not been completed.
private var reactionTimeResultsView: some View {
Group {
if reactionTimeResults.isEmpty {
notCompletedView(testName: "Reaction Time Test")
} else {
ResultsViz(
data: reactionTimeResults,
xName: "Time",
yName: "Results",
title: String(localized: "REACTIONTIME_VIZ_TITLE")
)
}
}
}

// Initializes the view with a binding to control whether the account UI is being presented.
init(presentingAccount: Binding<Bool>) {
Expand All @@ -174,6 +219,12 @@ struct Assessments: View {
assessmentsIP = true
showingTestSheet.toggle()
}
// Function to set up and start the ReactionTime Test.
func startReactionTimeTest() {
currentTest = Assessments.reactionTime
assessmentsIP = true
showingTestSheet.toggle()
}

// A view for displaying a message indicating that a specific assessment has not been completed.
private func notCompletedView(testName: String) -> some View {
Expand Down
82 changes: 82 additions & 0 deletions PICS/Assessment/ReactionTime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// This source file is part of the PICS based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2024 Stanford University
//
// SPDX-License-Identifier: MIT
//

import ResearchKit
import ResearchKitSwiftUI
import SwiftUI

struct ReactionTimeView: View {
@AppStorage("reactionTimeResults") private var reactionTimeResults: [AssessmentResult] = []
@AppStorage("AssessmentsInProgress") private var assessmentsIP = false
@Environment(\.presentationMode) var presentationMode

var body: some View {
ZStack {
Color(red: 242 / 255, green: 242 / 255, blue: 247 / 255)
.edgesIgnoringSafeArea(.all)
// Displays the ResearchKit ordered task view for the ReactionTime Test.
ORKOrderedTaskView(
tasks: createReactionTimeTask(),
tintColor: .accentColor,
shouldConfirmCancel: true,
result: handleTaskResult
)
}
}

// Creates the ReactionTime task to be presented to the user
private func createReactionTimeTask() -> ORKOrderedTask {
// Initializes a ReactionTime task with the following specified parameters
let task = ORKOrderedTask.reactionTime(
withIdentifier: "ReactionTimeTask",
intendedUseDescription: nil,
maximumStimulusInterval: 2.0,
minimumStimulusInterval: 1.0,
thresholdAcceleration: 0.8,
numberOfAttempts: 1,
timeout: 5.0,
successSound: 0,
timeoutSound: 0,
failureSound: 0,
options: [.excludeConclusion]
)
return task
}
// Handles the result of the ReactionTime task.
private func handleTaskResult(result: TaskResult) async {
let curTime = ProcessInfo.processInfo.systemUptime
assessmentsIP = false // End the assessment
// Adding this logic to dismiss the view
DispatchQueue.main.async {
self.presentationMode.wrappedValue.dismiss()
}
guard case let .completed(taskResult) = result else {
// Failed or canceled test. Do nothing for current.
return
}
// Fields to record the aggregated test results.
var totalTime: TimeInterval = 0
// Extract and process the ReactionTime test results.
for result in taskResult.results ?? [] {
if let stepResult = result as? ORKStepResult,
stepResult.identifier == "reactionTime" {
for reactionTimeResult in stepResult.results ?? [] {
if let curResult = reactionTimeResult as? ORKReactionTimeResult {
// Calculates the total time taken to complete the test.
totalTime += curTime - curResult.timestamp
}
}
}
}
reactionTimeResults += [AssessmentResult(testDateTime: Date(), timeSpent: totalTime)]
}
}

#Preview {
ReactionTimeView()
}
13 changes: 9 additions & 4 deletions PICS/Assessment/ResultsViz.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,22 @@ struct ResultsViz: View {
} else {
elm.errorCnt
}
let metricText = if metricNum < 0 {
""
} else {
", " +
self.metricType +
": " +
String(metricNum)
}
return (
prefix +
String(elm.testDateTime.formatted(.dateTime.year().month().day())) +
":\n" +
self.timeSpentLable +
": " +
String(timeSpent) +
", " +
self.metricType +
": " +
String(metricNum)
metricText
)
}

Expand Down
22 changes: 22 additions & 0 deletions PICS/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,28 @@
}
}
},
"REACTIONTIME_TEST" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Reaction Time Test"
}
}
}
},
"REACTIONTIME_VIZ_TITLE" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Reaction Time Results"
}
}
}
},
"Repository Link" : {
"localizations" : {
"en" : {
Expand Down

0 comments on commit 501b616

Please sign in to comment.