generated from StanfordSpezi/SpeziTemplateApplication
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Study Enrollment via Invitation Code Authentication and Customize Onboarding ## ♻️ Current situation & Problem Currently, there is no way to validate the identity of the user of the app to enroll them in the study. ## ⚙️ Release Notes - Customized Spezi Onboarding Flow to have language and symbols relevant to Engage - Integrated invitation code authorization into the onboarding flow, using cloud functions to validate supplied code against a database of valid codes stored in Firebase. ## 📚 Documentation Integrated code based on: StanfordBDHG/PediatricAppleWatchStudy#54. The Onboarding flow now has custom views: <img width="256" alt="Screenshot 2024-04-09 at 10 24 14 PM" src="https://github.com/StanfordBDHG/ENGAGE-HF/assets/108841122/ee4b1fe0-d560-4918-8de0-28be1d7aa7b3"> <img width="256" alt="Screenshot 2024-04-09 at 10 26 21 PM" src="https://github.com/StanfordBDHG/ENGAGE-HF/assets/108841122/a06f18b1-a944-4008-968b-0eab1aa36ca0"> <img width="256" alt="Screenshot 2024-04-09 at 10 26 28 PM" src="https://github.com/StanfordBDHG/ENGAGE-HF/assets/108841122/57d05f25-86d5-409c-8d92-31f25c7a17ed"> See SpeziOnboarding for documentation of the other views. ## ✅ Testing Thoroughly tested the onboarding flow, including the invitation code authorization and account creation process. ### Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md): - [X] I agree to follow the [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md). --------- Co-authored-by: Paul Schmiedmayer <[email protected]>
- Loading branch information
1 parent
db813e6
commit 15b47f6
Showing
29 changed files
with
7,789 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | ||
|
||
Files: firebase/* | ||
Copyright: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
License: MIT | ||
Comment: All files are part of the ENGAGE-HF application as seeded data for the Firebase emulator. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
// Based on: https://github.com/StanfordBDHG/PediatricAppleWatchStudy/pull/54/files | ||
// | ||
|
||
import Foundation | ||
|
||
|
||
enum InvitationCodeError: LocalizedError { | ||
case invitationCodeInvalid | ||
case userNotAuthenticated | ||
case generalError(String) | ||
|
||
|
||
var errorDescription: String? { | ||
switch self { | ||
case .invitationCodeInvalid: | ||
String(localized: "The invitation code is invalid or has already been used.", comment: "Invitation Code Invalid") | ||
case .userNotAuthenticated: | ||
String(localized: "User authentication failed. Please try to sign in again.", comment: "User Not Authenticated") | ||
case .generalError(let message): | ||
String(localized: "An error occurred: \(message)", comment: "General Error") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// | ||
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import Firebase | ||
import FirebaseAuth | ||
import FirebaseFunctions | ||
import SpeziOnboarding | ||
import SpeziValidation | ||
import SpeziViews | ||
import SwiftUI | ||
|
||
|
||
struct InvitationCodeView: View { | ||
@Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath | ||
@State private var invitationCode = "" | ||
@State private var viewState: ViewState = .idle | ||
@ValidationState private var validation | ||
|
||
|
||
var body: some View { | ||
ScrollView { | ||
VStack(spacing: 32) { | ||
invitationCodeHeader | ||
Divider() | ||
Grid(horizontalSpacing: 16, verticalSpacing: 16) { | ||
invitationCodeView | ||
} | ||
.padding(.top, -8) | ||
.padding(.bottom, -12) | ||
Divider() | ||
OnboardingActionsView( | ||
primaryText: "Redeem Invitation Code", | ||
primaryAction: { | ||
guard validation.validateSubviews() else { | ||
return | ||
} | ||
|
||
await verifyOnboardingCode() | ||
}, | ||
secondaryText: "I Already Have an Account", | ||
secondaryAction: { | ||
try Auth.auth().signOut() | ||
onboardingNavigationPath.nextStep() | ||
} | ||
) | ||
} | ||
.padding(.horizontal) | ||
.padding(.bottom) | ||
.viewStateAlert(state: $viewState) | ||
.navigationBarTitleDisplayMode(.large) | ||
.navigationTitle(String(localized: "Invitation Code")) | ||
} | ||
} | ||
|
||
|
||
@ViewBuilder private var invitationCodeView: some View { | ||
DescriptionGridRow { | ||
Text("Invitation Code") | ||
} content: { | ||
VerifiableTextField( | ||
LocalizedStringResource("Invitation Code"), | ||
text: $invitationCode | ||
) | ||
.autocorrectionDisabled(true) | ||
.textInputAutocapitalization(.characters) | ||
.textContentType(.oneTimeCode) | ||
.validate(input: invitationCode, rules: [invitationCodeValidationRule]) | ||
} | ||
.receiveValidation(in: $validation) | ||
} | ||
|
||
@ViewBuilder private var invitationCodeHeader: some View { | ||
VStack(spacing: 32) { | ||
Image(systemName: "rectangle.and.pencil.and.ellipsis") | ||
.resizable() | ||
.scaledToFit() | ||
.frame(height: 100) | ||
.accessibilityHidden(true) | ||
.foregroundStyle(Color.accentColor) | ||
Text("Please enter your invitation code to join the ENGAGE-HF study.") | ||
} | ||
} | ||
|
||
private var invitationCodeValidationRule: ValidationRule { | ||
ValidationRule( | ||
rule: { invitationCode in | ||
invitationCode.count >= 8 | ||
}, | ||
message: "An invitation code is at least 8 characters long." | ||
) | ||
} | ||
|
||
init() { | ||
if FeatureFlags.useFirebaseEmulator { | ||
Functions.functions().useEmulator(withHost: "localhost", port: 5001) | ||
} | ||
} | ||
|
||
private func verifyOnboardingCode() async { | ||
do { | ||
if FeatureFlags.disableFirebase { | ||
guard invitationCode == "VASCTRAC" else { | ||
throw InvitationCodeError.invitationCodeInvalid | ||
} | ||
|
||
try? await Task.sleep(for: .seconds(0.25)) | ||
} else { | ||
try Auth.auth().signOut() | ||
|
||
async let authResult = Auth.auth().signInAnonymously() | ||
let checkInvitationCode = Functions.functions().httpsCallable("checkInvitationCode") | ||
|
||
do { | ||
_ = try await checkInvitationCode.call( | ||
[ | ||
"invitationCode": invitationCode, | ||
"userId": authResult.user.uid | ||
] | ||
) | ||
} catch { | ||
throw InvitationCodeError.invitationCodeInvalid | ||
} | ||
} | ||
|
||
await onboardingNavigationPath.nextStep() | ||
} catch let error as NSError { | ||
if let errorCode = FunctionsErrorCode(rawValue: error.code) { | ||
// Handle Firebase-specific errors. | ||
switch errorCode { | ||
case .unauthenticated: | ||
viewState = .error(InvitationCodeError.userNotAuthenticated) | ||
case .notFound: | ||
viewState = .error(InvitationCodeError.invitationCodeInvalid) | ||
default: | ||
viewState = .error(InvitationCodeError.generalError(error.localizedDescription)) | ||
} | ||
} else { | ||
// Handle other errors, such as network issues or unexpected behavior. | ||
viewState = .error(InvitationCodeError.generalError(error.localizedDescription)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
#Preview { | ||
FirebaseApp.configure() | ||
|
||
return OnboardingStack { | ||
InvitationCodeView() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
Spezi can render consent documents in the markdown format: This is a *markdown* **example**. | ||
The ENGAGE-HF iOS Mobile Application will connect to external devices via Bluetooth | ||
to record personal health information, including weight, heart rate, and blood pressure. | ||
|
||
Your personal information will only be shared with the research team conducting the study. |
Oops, something went wrong.