diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 631f94d..de9e197 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,6 +20,12 @@ jobs: swiftlint: name: SwiftLint uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2 + codeql: + name: CodeQL + uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + with: + codeql: true + fastlanelane: codeql buildandtest: name: Build and Test uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 diff --git a/.swiftlint.yml b/.swiftlint.yml index 879bfc9..72c79de 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -377,6 +377,7 @@ deployment_target: # Availability checks or attributes shouldn’t be using olde excluded: # paths to ignore during linting. Takes precedence over `included`. - .build - .swiftpm + - .codeql - .derivedData closure_body_length: # Closure bodies should not span too many lines. diff --git a/NAMS.xcodeproj/project.pbxproj b/NAMS.xcodeproj/project.pbxproj index 1c48811..5ea2c06 100644 --- a/NAMS.xcodeproj/project.pbxproj +++ b/NAMS.xcodeproj/project.pbxproj @@ -58,6 +58,8 @@ 653A256228338800005D4D48 /* NAMSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* NAMSTests.swift */; }; A91459762A4AF4E000A04641 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91459752A4AF4E000A04641 /* SchedulerTests.swift */; }; A916ADD52AB60227006960DF /* NotificationPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD42AB60227006960DF /* NotificationPermissions.swift */; }; + A916ADD72AB62012006960DF /* ProcessInfo+PreviewSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */; }; + A916ADD92AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */; }; A9610AB72AB4847400E46ADE /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = A9610AB62AB4847400E46ADE /* SpeziMockWebService */; }; A9610AB92AB4868D00E46ADE /* NAMSStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9610AB82AB4868D00E46ADE /* NAMSStandard.swift */; }; A9610ABB2AB49F0900E46ADE /* MockUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9610ABA2AB49F0900E46ADE /* MockUploadTests.swift */; }; @@ -124,6 +126,8 @@ 653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A91459752A4AF4E000A04641 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = ""; }; A916ADD42AB60227006960DF /* NotificationPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissions.swift; sourceTree = ""; }; + A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+PreviewSimulator.swift"; sourceTree = ""; }; + A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingFlow+PreviewSimulator.swift"; sourceTree = ""; }; A9610AB82AB4868D00E46ADE /* NAMSStandard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NAMSStandard.swift; sourceTree = ""; }; A9610ABA2AB49F0900E46ADE /* MockUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -202,6 +206,7 @@ 2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */, 2DC173772F6FD3C05EBFCE52 /* FinishedSetup.swift */, A916ADD42AB60227006960DF /* NotificationPermissions.swift */, + A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */, ); path = Onboarding; sourceTree = ""; @@ -247,6 +252,7 @@ 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */, 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */, 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */, + A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */, ); path = Helper; sourceTree = ""; @@ -530,6 +536,8 @@ 2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */, 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */, 2FE5DCB229EE6107004B9AB4 /* NAMSLogin.swift in Sources */, + A916ADD72AB62012006960DF /* ProcessInfo+PreviewSimulator.swift in Sources */, + A916ADD92AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift in Sources */, 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, diff --git a/NAMS/Helper/ProcessInfo+PreviewSimulator.swift b/NAMS/Helper/ProcessInfo+PreviewSimulator.swift new file mode 100644 index 0000000..1386661 --- /dev/null +++ b/NAMS/Helper/ProcessInfo+PreviewSimulator.swift @@ -0,0 +1,15 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// +import Foundation + + +extension ProcessInfo { + var isPreviewSimulator: Bool { + environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" + } +} diff --git a/NAMS/Onboarding/AccountSetup/AccountSetup.swift b/NAMS/Onboarding/AccountSetup/AccountSetup.swift index 42e665f..be7aca9 100644 --- a/NAMS/Onboarding/AccountSetup/AccountSetup.swift +++ b/NAMS/Onboarding/AccountSetup/AccountSetup.swift @@ -14,10 +14,10 @@ import SwiftUI struct AccountSetup: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] @EnvironmentObject private var account: Account - @EnvironmentObject private var scheduler: NAMSScheduler - @EnvironmentObject private var standard: NAMSStandard + @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath + + @State private var signingOutPretrigger = false var body: some View { OnboardingView( @@ -37,10 +37,12 @@ struct AccountSetup: View { } ) .onReceive(account.objectWillChange) { - if account.signedIn { - Task { @MainActor in - await moveToNextOnboardingStep() + if !signingOutPretrigger { + if account.signedIn { + onboardingNavigationPath.nextStep() } + } else { + signingOutPretrigger = false } } } @@ -77,9 +79,7 @@ struct AccountSetup: View { UserView() .padding() Button("Logout", role: .destructive) { - // workaround as of https://github.com/StanfordSpezi/SpeziTemplateApplication/issues/21 - account.signedIn = false - + signingOutPretrigger = true try? Auth.auth().signOut() } } @@ -91,58 +91,33 @@ struct AccountSetup: View { OnboardingActionsView( "ACCOUNT_NEXT", action: { - await moveToNextOnboardingStep() + onboardingNavigationPath.nextStep() } ) } else { OnboardingActionsView( primaryText: "ACCOUNT_SIGN_UP", primaryAction: { - onboardingSteps.append(.signUp) + onboardingNavigationPath.append(customView: NAMSSignUp()) }, secondaryText: "ACCOUNT_LOGIN", secondaryAction: { - onboardingSteps.append(.login) + onboardingNavigationPath.append(customView: NAMSLogin()) } ) } } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } - - - @MainActor - private func moveToNextOnboardingStep() async { - if await !scheduler.localNotificationAuthorization { - onboardingSteps.append(.notificationPermissions) - } else { - onboardingSteps.append(.finished) - } - - await standard.signedIn() - - // Unfortunately, SwiftUI currently animates changes in the navigation path that do not change - // the current top view. Therefore we need to do the following async procedure to remove the - // `.login` and `.signUp` steps while disabling the animations before and re-enabling them - // after the elements have been changed. - try? await Task.sleep(for: .seconds(1.0)) - UIView.setAnimationsEnabled(false) - onboardingSteps.removeAll(where: { $0 == .login || $0 == .signUp }) - try? await Task.sleep(for: .seconds(1.0)) - UIView.setAnimationsEnabled(true) - } } #if DEBUG struct AccountSetup_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - static var previews: some View { - AccountSetup(onboardingSteps: $path) + OnboardingStack(startAtStep: AccountSetup.self) { + for onboardingView in OnboardingFlow.previewSimulatorViews { + onboardingView + } + } .environmentObject(Account(accountServices: [])) .environmentObject(FirebaseAccountConfiguration(emulatorSettings: (host: "localhost", port: 9099))) } diff --git a/NAMS/Onboarding/AccountSetup/IconView.swift b/NAMS/Onboarding/AccountSetup/IconView.swift index 4d3ec72..09696c1 100644 --- a/NAMS/Onboarding/AccountSetup/IconView.swift +++ b/NAMS/Onboarding/AccountSetup/IconView.swift @@ -10,7 +10,7 @@ import SwiftUI struct IconView: View { - let size: Double + private let size: Double var body: some View { Image(uiImage: Bundle.main.image(withName: "AppIcon", fileExtension: "png")) diff --git a/NAMS/Onboarding/AccountSetup/UserView.swift b/NAMS/Onboarding/AccountSetup/UserView.swift index eba740c..d7ccf1a 100644 --- a/NAMS/Onboarding/AccountSetup/UserView.swift +++ b/NAMS/Onboarding/AccountSetup/UserView.swift @@ -13,8 +13,8 @@ import SwiftUI struct UserView: View { - @EnvironmentObject var account: Account - @EnvironmentObject var firebaseAccountConfiguration: FirebaseAccountConfiguration + @EnvironmentObject private var account: Account + @EnvironmentObject private var firebaseAccountConfiguration: FirebaseAccountConfiguration var body: some View { @@ -57,7 +57,7 @@ struct UserView: View { #if DEBUG -struct SwiftUIView_Previews: PreviewProvider { +struct UserView_Previews: PreviewProvider { static var previews: some View { UserView() .padding() diff --git a/NAMS/Onboarding/FinishedSetup.swift b/NAMS/Onboarding/FinishedSetup.swift index 86d8811..8f73db5 100644 --- a/NAMS/Onboarding/FinishedSetup.swift +++ b/NAMS/Onboarding/FinishedSetup.swift @@ -11,8 +11,7 @@ import SpeziOnboarding import SwiftUI struct FinishedSetup: View { - @AppStorage(StorageKeys.onboardingFlowComplete) - var completedOnboardingFlow = false + @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath var body: some View { OnboardingView( @@ -42,7 +41,7 @@ struct FinishedSetup: View { }, actionView: { OnboardingActionsView("Start") { - completedOnboardingFlow = true + onboardingNavigationPath.nextStep() } } ) diff --git a/NAMS/Onboarding/NotificationPermissions.swift b/NAMS/Onboarding/NotificationPermissions.swift index 258cc0d..f4511d4 100644 --- a/NAMS/Onboarding/NotificationPermissions.swift +++ b/NAMS/Onboarding/NotificationPermissions.swift @@ -12,9 +12,10 @@ import SwiftUI struct NotificationPermissions: View { - @EnvironmentObject var scheduler: NAMSScheduler - @Binding private var onboardingSteps: [OnboardingFlow.Step] - @State var notificationProcessing = false + @EnvironmentObject private var scheduler: NAMSScheduler + @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath + + @State private var notificationProcessing = false var body: some View { @@ -41,8 +42,8 @@ struct NotificationPermissions: View { action: { do { notificationProcessing = true - // notifications are not available in the preview simulator. - if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + // Notification Authorization are not available in the preview simulator. + if ProcessInfo.processInfo.isPreviewSimulator { try await _Concurrency.Task.sleep(for: .seconds(5)) } else { try await scheduler.requestLocalNotificationAuthorization() @@ -50,28 +51,27 @@ struct NotificationPermissions: View { } catch { print("Could not request notification permissions.") } + notificationProcessing = false - onboardingSteps.append(.finished) + onboardingNavigationPath.nextStep() } ) } ) - .navigationBarBackButtonHidden(notificationProcessing) - } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps + .navigationBarBackButtonHidden(notificationProcessing) + .navigationTitle("") // // Small fix as otherwise "Login" or "Sign up" is still shown in the nav bar } } #if DEBUG struct NotificationPermissions_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - static var previews: some View { - NotificationPermissions(onboardingSteps: $path) + OnboardingStack(startAtStep: NotificationPermissions.self) { + for onboardingView in OnboardingFlow.previewSimulatorViews { + onboardingView + } + } } } #endif diff --git a/NAMS/Onboarding/OnboardingFlow+PreviewSimulator.swift b/NAMS/Onboarding/OnboardingFlow+PreviewSimulator.swift new file mode 100644 index 0000000..7d0d41e --- /dev/null +++ b/NAMS/Onboarding/OnboardingFlow+PreviewSimulator.swift @@ -0,0 +1,16 @@ +// +// This source file is part of the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// +import SwiftUI + + +/// Defines onboarding views that are shown in the Xcode preview simulator +extension OnboardingFlow { + static let previewSimulatorViews: [any View] = { + [Welcome(), AccountSetup(), NotificationPermissions(), FinishedSetup()] + }() +} diff --git a/NAMS/Onboarding/OnboardingFlow.swift b/NAMS/Onboarding/OnboardingFlow.swift index 660f170..bbfd8b9 100644 --- a/NAMS/Onboarding/OnboardingFlow.swift +++ b/NAMS/Onboarding/OnboardingFlow.swift @@ -8,44 +8,37 @@ import SpeziAccount import SpeziFirebaseAccount +import SpeziOnboarding import SwiftUI /// Displays an multi-step onboarding flow for the Neurodevelopment Assessment and Monitoring System (NAMS). struct OnboardingFlow: View { - enum Step: String, Codable { - case accountSetup - case login - case signUp - case notificationPermissions - case finished - } - - @SceneStorage(StorageKeys.onboardingFlowStep) - private var onboardingSteps: [Step] = [] + @EnvironmentObject private var scheduler: NAMSScheduler + @AppStorage(StorageKeys.onboardingFlowComplete) - var completedOnboardingFlow = false + private var completedOnboardingFlow = false + + @State private var localNotificationAuthorization = false var body: some View { - NavigationStack(path: $onboardingSteps) { - Welcome(onboardingSteps: $onboardingSteps) - .navigationDestination(for: Step.self) { onboardingStep in - switch onboardingStep { - case .accountSetup: - AccountSetup(onboardingSteps: $onboardingSteps) - case .login: - NAMSLogin() - case .signUp: - NAMSSignUp() - case .notificationPermissions: - NotificationPermissions(onboardingSteps: $onboardingSteps) - case .finished: - FinishedSetup() - } - } - .navigationBarTitleDisplayMode(.inline) + OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) { + Welcome() + + if !FeatureFlags.disableFirebase { + AccountSetup() + } + + if !localNotificationAuthorization { + NotificationPermissions() + } + + FinishedSetup() } - .interactiveDismissDisabled(!completedOnboardingFlow) + .task { + localNotificationAuthorization = await scheduler.localNotificationAuthorization + } + .interactiveDismissDisabled(!completedOnboardingFlow) } } diff --git a/NAMS/Onboarding/Welcome.swift b/NAMS/Onboarding/Welcome.swift index b47930e..f111bc6 100644 --- a/NAMS/Onboarding/Welcome.swift +++ b/NAMS/Onboarding/Welcome.swift @@ -10,7 +10,7 @@ import SpeziOnboarding import SwiftUI struct Welcome: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] + @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath var body: some View { @@ -36,25 +36,21 @@ struct Welcome: View { ], actionText: "WELCOME_BUTTON", action: { - onboardingSteps.append(.accountSetup) + onboardingNavigationPath.nextStep() } ) } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } } #if DEBUG struct Welcome_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - static var previews: some View { - Welcome(onboardingSteps: $path) + OnboardingStack(startAtStep: Welcome.self) { + for onboardingView in OnboardingFlow.previewSimulatorViews { + onboardingView + } + } } } #endif diff --git a/NAMS/Resources/SocialSupportQuestionnaire.json b/NAMS/Resources/SocialSupportQuestionnaire.json index 9981799..8a79098 100644 --- a/NAMS/Resources/SocialSupportQuestionnaire.json +++ b/NAMS/Resources/SocialSupportQuestionnaire.json @@ -1,9 +1,11 @@ { - "title": "Social Support", - "description": "This survey measures tangible social support plus a couple of demographic questions.", "resourceType": "Questionnaire", "language": "en-US", + "id": "socialsupport", "name": "SocialSupport", + "title": "Social Support", + "description": "This survey measures tangible social support plus a couple of demographic questions.", + "version": "1", "status": "draft", "publisher": "RAND Corp", "meta": { @@ -37,18 +39,16 @@ ], "contact": [ { - "name": "http://spezi.stanford.eduhttps://www.rand.org/health-care/surveys_tools/mos/social-support/survey-instrument.html" + "name": "https://www.rand.org/health-care/surveys_tools/mos/social-support/survey-instrument.html" } ], "subjectType": [ "Patient" ], - "url": "http://spezi.stanford.edu/fhir/questionnaire/32f43c8e-93e9-4c70-97a0-e716f8030073", - "id": "socialsupport", - "version": "1", - "date": "2023-01-23T00:00:00-08:00", "purpose": "The RAND Medical Outcomes Social Support survey is a 4-item questionnaire that measures social support.", "copyright": "RAND Corp surveys are open-source and free to use.", + "date": "2023-01-23T00:00:00-08:00", + "url": "http://spezi.stanford.edu/fhir/questionnaire/32f43c8e-93e9-4c70-97a0-e716f8030073", "item": [ { "linkId": "dcea2683-9815-4505-b240-e75b502b29ef", @@ -256,7 +256,7 @@ "valueInteger": 120 }, { - "url": "http://ehelse.no/fhir/StructureDefinition/validationtext", + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", "valueString": "Please enter a valid age." } ], @@ -266,73 +266,6 @@ "linkId": "695525f3-3e89-4455-8e25-878171c596da", "type": "choice", "text": "What is your preferred contact method?", - "item": [ - { - "linkId": "86290b0a-017e-4193-8707-dc0c2146f0eb", - "type": "string", - "text": "What is your e-mail?", - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" - }, - { - "url": "http://ehelse.no/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid email" - }, - { - "url": "http://hl7.org/fhir/StructureDefinition/minLength", - "valueInteger": 1 - } - ], - "required": false, - "maxLength": 50, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "e-mail" - } - } - ] - }, - { - "linkId": "c3bea33d-4c50-4f4a-8ae4-1a52be326b19", - "type": "string", - "text": "What is your phone number? Ex. (555) 555-5555", - "required": false, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "phone-call" - } - }, - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "text-message" - } - } - ], - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "(xxx) xxx-xxxxx" - }, - { - "url": "http://ehelse.no/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid phone number." - } - ] - } - ], "required": false, "answerOption": [ { @@ -360,6 +293,95 @@ } } ] + }, + { + "linkId": "c3bea33d-4c50-4f4a-8ae4-1a52be326b19", + "type": "string", + "text": "What is your phone number? Ex. (555) 555-5555", + "required": false, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "phone-call" + } + } + ], + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid phone number." + } + ] + }, + { + "linkId": "8e906a39-5fd0-42a8-f42c-bd96d719dd13", + "type": "string", + "text": "What is your text number? Ex. (555) 555-5555", + "required": false, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "text-message" + } + } + ], + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid phone number." + } + ] + }, + { + "linkId": "86290b0a-017e-4193-8707-dc0c2146f0eb", + "type": "string", + "text": "What is your e-mail?", + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": ".*@.+" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid email" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/minLength", + "valueInteger": 1 + } + ], + "required": false, + "maxLength": 50, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "e-mail" + } + } + ] + }, + { + "linkId": "305f5381-2d8b-4b98-bc04-5a39bee2f7ec", + "type": "display", + "text": "Thank you for taking the survey!", + "required": false } ] -} +} \ No newline at end of file diff --git a/NAMSUITests/OnboardingTests.swift b/NAMSUITests/OnboardingTests.swift index 6abf350..ea72744 100644 --- a/NAMSUITests/OnboardingTests.swift +++ b/NAMSUITests/OnboardingTests.swift @@ -71,7 +71,7 @@ extension XCUIApplication { } private func navigateOnboardingFlowWelcome() throws { - XCTAssertTrue(staticTexts["Neurodevelopment Assessment and Monitoring System (NAMS)"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts["Neurodevelopment Assessment and Monitoring System (NAMS)"].waitForExistence(timeout: 5)) let continueButton = buttons["Continue"] XCTAssertTrue(continueButton.waitForExistence(timeout: 2)) @@ -88,7 +88,7 @@ extension XCUIApplication { } func navigateOnboardingWithoutAccount() throws { - XCTAssertTrue(staticTexts["Your Account"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts["Your Account"].waitForExistence(timeout: 5)) XCTAssertTrue(buttons["Sign Up"].waitForExistence(timeout: 2)) buttons["Sign Up"].tap() @@ -123,7 +123,7 @@ extension XCUIApplication { } private func navigateOnboardingFlowNotification() throws { - XCTAssertTrue(staticTexts["Notifications"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts["Notifications"].waitForExistence(timeout: 5)) XCTAssertTrue(buttons["Continue"].waitForExistence(timeout: 2)) buttons["Continue"].tap() diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 56a1e7e..28a4327 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -24,6 +24,15 @@ platform :ios do ) end + desc "CodeQL" + lane :codeql do + build_app( + skip_archive: true, + skip_codesigning: true, + derived_data_path: ".derivedData" + ) + end + desc "Build app" lane :build do build_app(