### Package Setup and AppDelegate Registration Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Integrate SpeziAccount into your project by adding the Swift package dependency and registering AccountConfiguration in your SpeziAppDelegate. This example shows basic setup with an InMemoryAccountService. ```swift // Package.swift dependency .package(url: "https://github.com/StanfordSpezi/SpeziAccount.git", from: "2.0.0") // AppDelegate.swift import Spezi import SpeziAccount class MyAppDelegate: SpeziAppDelegate { override var configuration: Configuration { Configuration(standard: MyStandard()) { AccountConfiguration( service: InMemoryAccountService(), // swap for FirebaseAccountService, etc. configuration: [ .requires(\.userId), .requires(\.password), .requires(\.name), .collects(\.dateOfBirth), .collects(\.genderIdentity) ] ) } } } ``` -------------------------------- ### Define Custom Account Service with Identity Providers Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Creating your own Account Service.md Implement the AccountService protocol and define identity providers using the @IdentityProvider property wrapper. This example shows how to set up both a primary setup view and a Sign in with Apple view, with the ability to dynamically enable or disable providers. ```swift import AuthenticationServices import Spezi import SpeziAccount private struct MySetupView: View { @Environment(MyAccountService.self) private var service var body: some View { // An AccountSetupProviderView that doesn't support password reset functionality. AccountSetupProviderView { credentials in try await service.login(userId: credentials.userId, password: credentials.password) } signup: { details in try await service.signup(details) } } nonisolated init() {} } private struct SignInWithAppleView: View { @Environment(MyAccountService.self) private var service var body: some View { SignInWithAppleButton { request in service.handleRequest(request) } onCompletion: { result in service.handleCompletion(result) } } nonisolated init() {} } public actor MyAccountService: AccountService { public let configuration = AccountServiceConfiguration(supportedKeys: .arbitrary) @Dependency(Account.self) private var account @IdentityProvider(section: .primary) private var primarySetup = MySetupView() @IdentityProvider(enabled: false, section: .singleSignOn) private var signInWithApple = SignInWithAppleView() public init(supportsApple: Bool = false) { if supportsApple { $signInWithApple.isEnabled = true } } public func login(userId: String, password: String) async throws { // handle login with the provided credentials } public func signup(_ details: AccountDetails) async throws { // handle signup with provided details } @MainActor fileprivate func handleRequest(_ request: ASAuthorizationAppleIDRequest) { // set requested scopes and generate a nonce } @MainActor fileprivate func handleCompletion(_ result: Result) { // handle result } } ``` -------------------------------- ### Display Account Setup View Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/Setup Guides/Initial Setup.md Use the AccountSetup view to present identity providers for account creation. Access the Account module via @Environment to check sign-in status and details. ```swift struct MyView: View { @Environment(Account.self) var account var body: some View { AccountSetup() } } ``` -------------------------------- ### Configuring an AccountService Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Creating your own Account Service.md Shows how to configure an AccountService with specific requirements. This example sets up arbitrary key support, defines a UserIdConfiguration for email, and specifies required keys for userId and password. ```swift AccountServiceConfiguration(supportedKeys: .arbitrary) { UserIdConfiguration.emailAddress // userId key has an "email" label and uses the email keyboard RequiredAccountKeys { \.userId \.password } } ``` -------------------------------- ### AccountConfiguration with Different Services Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Demonstrates setting up AccountConfiguration with different account services and storage providers. The first example uses a Firebase account service, while the second integrates an external storage provider for specific account keys. ```swift import Spezi import SpeziAccount // Basic setup – service stores all keys natively. class AppDelegate: SpeziAppDelegate { override var configuration: Configuration { Configuration(standard: MyStandard()) { AccountConfiguration( service: MyFirebaseAccountService(), configuration: [ .requires(\.userId), .requires(\.password), .requires(\.name), .collects(\.dateOfBirth), .collects(\.genderIdentity) ] ) } } } // With an external storage provider for keys the service cannot store. class AppDelegateWithStorage: SpeziAppDelegate { override var configuration: Configuration { Configuration(standard: MyStandard()) { AccountConfiguration( service: MyLimitedAccountService(), storageProvider: MyFirestoreStorageProvider(), configuration: [ .requires(\.userId), .requires(\.name), .supports(\.biography) // stored by the provider, not the service ] ) } } } ``` -------------------------------- ### Implement Custom Account Storage with MyFirestoreStorageProvider Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Implement `AccountStorageProvider` to persist `AccountKey` values that the primary `AccountService` cannot store natively. This example uses Firestore for storage and a local cache. ```swift import Spezi import SpeziAccount public actor MyFirestoreStorageProvider: AccountStorageProvider { @Dependency(ExternalAccountStorage.self) private var externalStorage public func load(_ accountId: String, _ keys: [any AccountKey.Type]) async -> AccountDetails? { // Return immediately from cache; return nil if a network call is needed. if let cached = localCache[accountId] { return cached } // Trigger async fetch and notify later. Task { let fetched = try? await fetchFromFirestore(accountId, keys) if let fetched { await externalStorage.notifyAboutUpdatedDetails(for: accountId, fetched) } } return nil } public func store(_ accountId: String, _ modifications: AccountModifications) async throws { let data = try encode(modifications.modifiedDetails) try await firestoreDocument(for: accountId).setData(data, merge: true) localCache[accountId] = modifications.modifiedDetails } public func disassociate(_ accountId: String) async { localCache.removeValue(forKey: accountId) } public func delete(_ accountId: String) async throws { try await firestoreDocument(for: accountId).delete() localCache.removeValue(forKey: accountId) } } ``` -------------------------------- ### AccountStorageProvider Protocol Implementation Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Example implementation of the AccountStorageProvider protocol for persisting account data, such as custom fields, using Firestore. ```APIDOC ## AccountStorageProvider Protocol Implement `AccountStorageProvider` to persist `AccountKey` values that the primary `AccountService` cannot store natively (e.g., custom fields in Firestore). ```swift import Spezi import SpeziAccount public actor MyFirestoreStorageProvider: AccountStorageProvider { @Dependency(ExternalAccountStorage.self) private var externalStorage public func load(_ accountId: String, _ keys: [any AccountKey.Type]) async -> AccountDetails? { // Return immediately from cache; return nil if a network call is needed. if let cached = localCache[accountId] { return cached } // Trigger async fetch and notify later. Task { let fetched = try? await fetchFromFirestore(accountId, keys) if let fetched { await externalStorage.notifyAboutUpdatedDetails(for: accountId, fetched) } } return nil } public func store(_ accountId: String, _ modifications: AccountModifications) async throws { let data = try encode(modifications.modifiedDetails) try await firestoreDocument(for: accountId).setData(data, merge: true) localCache[accountId] = modifications.modifiedDetails } public func disassociate(_ accountId: String) async { localCache.removeValue(forKey: accountId) } public func delete(_ accountId: String) async throws { try await firestoreDocument(for: accountId).delete() localCache.removeValue(forKey: accountId) } } ``` ``` -------------------------------- ### Implement PhoneVerificationConstraint Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccountPhoneNumbers/SpeziAccountPhoneNumbers.docc/SpeziAccountPhoneNumbers.md Conform your `Standard` to `PhoneVerificationConstraint` to handle the logic for starting, completing, and deleting phone number verifications. ```swift actor YourStandard: PhoneVerificationConstraint { func startVerification(_ number: PhoneNumber) async throws { // Implement phone number verification start } func completeVerification(_ number: PhoneNumber, _ code: String) async throws { // Implement phone number verification completion } func delete(_ number: PhoneNumber) async throws { // Implement phone number deletion } } ``` -------------------------------- ### Configure Custom Phone Number Encoding for Firestore Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccountPhoneNumbers/SpeziAccountPhoneNumbers.docc/SpeziAccountPhoneNumbers.md Customize how phone numbers are encoded and decoded for storage, for example, to store only the E.164 string format. ```swift private var customEncoder: FirebaseFirestore.Firestore.Encoder { let encoder = FirebaseFirestore.Firestore.Encoder() encoder.userInfo[.phoneNumberEncodingStrategy] = PhoneNumberDecodingStrategy.e164 return encoder } private var customDecoder: FirebaseFirestore.Firestore.Decoder { let decoder = FirebaseFirestore.Firestore.Decoder() decoder.userInfo[.phoneNumberEncodingStrategy] = PhoneNumberDecodingStrategy.e164 return decoder } override var configuration: Configuration { Configuration(standard: YourStandard()) { AccountConfiguration( storageProvider: FirestoreAccountStorage( storeIn: Firestore.userCollection, mapping: [ "phoneNumbers": AccountKeys.phoneNumbers, // ... other mappings ... ], encoder: customEncoder, decoder: customDecoder ), // ... other configuration ... ) } } ``` -------------------------------- ### Declare a Custom AccountKey for Biography Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountKey/Adding new Account Values.md Define a new AccountKey for storing a user's biography. This example uses the @AccountKey macro to specify the name, category, and type of the account detail. ```swift extension AccountDetails { @AccountKey(name: "Biography", category: .personalDetails, as: String.self) var biography: String? } ``` -------------------------------- ### Secure Account Operations with SecurityRelatedModifier Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Creating your own Account Service.md Wrap sensitive account operations with the `SecurityRelatedModifier` to ensure user authentication before execution. This example demonstrates how to use the modifier to prompt for authentication before performing a delete operation. ```swift actor MyAccountService: AccountService { @SecurityRelatedModifier var myModifier = MyModifier() func delete() async throws { // pop up an alert that requires entering the password try await myModifier.ensureAuthenticated() // perform delete } } ``` -------------------------------- ### Using AccountSetup for Onboarding Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Shows how to integrate `AccountSetup` into an onboarding flow or present it as a standalone sheet. It manages the full sign-in/sign-up process and handles follow-up information sheets. ```swift import SpeziAccount import SwiftUI // Minimal usage in an onboarding flow. struct OnboardingAccountStep: View { @Environment(Account.self) private var account var body: some View { // When an account is already associated, AccountSetup shows the existing // account details and renders the `continue` closure. AccountSetup { // Called once setup is fully complete. print("Account setup finished") } continue: { NavigationLink("Continue") { HomeView() } .buttonStyle(.borderedProminent) } } } // Standalone sheet presentation. struct ContentView: View { @State private var showSetup = false var body: some View { Button("Sign In") { showSetup = true } .sheet(isPresented: $showSetup) { NavigationStack { AccountSetup(setupComplete: { details in showSetup = false print("Signed in as \(details.userId)") }) } } } } ``` -------------------------------- ### Implement a Custom AccountStorageProvider Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Custom Storage Provider.md Adopt the AccountStorageProvider protocol to create your own storage solution. Handle creation, loading, modification, disassociation, and deletion of account details. ```swift public actor MyProvider: AccountStorageProvider { @Dependency(ExternalAccountStorage.self) private var storage public func create(_ accountId: String, _ details: AccountDetails) async throws { // handle creation of a new record try await modify(accountId, AccountModifications(modifiedDetails: details)) } public func load(_ accountId: String, _ keys: [any AccountKey.Type]) async throws -> AccountDetails? { // Contact local cache if details are present. // If not, return `nil` and notify account service about details retrieved from remote service // by calling storage.notifyAboutUpdatedDetails(for:_:). } public func modify(_ accountId: String, _ modifications: AccountModifications) async throws { // update stored details } public func disassociate(_ accountId: String) async { // clear locally cached data } public func delete(_ accountId: String) async throws { // remove record and clear locally cached data } } ``` -------------------------------- ### Minimal AccountService Implementation Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Creating your own Account Service.md Demonstrates the minimal protocol requirements for an AccountService. It includes basic implementations for `logout`, `delete`, and `updateAccountDetails`, and importantly, emits the `deletingAccount` notification. ```swift import Spezi import SpeziAccount public actor MyAccountService: AccountService { public let configuration = AccountServiceConfiguration(supportedKeys: .arbitrary) @Dependency(Account.self) private var account @Dependency(AccountNotifications.self) private var notifications public init() {} public func logout() async throws { // remove local account association await account.removeUserDetails() } public func delete() async throws { guard let details = account.details else { return } // delete account details ... // emitting the event is mandatory try await notifications.reportEvent(.deletingAccount(details.accountId)) await account.removeUserDetails() } public func updateAccountDetails(_ modifications: AccountModifications) async throws { // update account details // call account.supplyUserDetails(_:) with updated account details } } ``` -------------------------------- ### Accessing Account State in SwiftUI and Modules Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Demonstrates how to access the `Account` observable object from SwiftUI views using `@Environment` and from other Spezi `Module`s using `@Dependency`. Use this to conditionally display UI or perform actions based on the user's sign-in status. ```swift import SpeziAccount import SwiftUI // --- From a SwiftUI view --- struct ProfileBanner: View { @Environment(Account.self) private var account var body: some View { if account.signedIn, let details = account.details { VStack(alignment: .leading) { Text(details.name?.formatted() ?? "No name") Text(details.userId) .font(.caption) .foregroundStyle(.secondary) } } else { Text("Not signed in") } } } // --- From a Spezi Module --- import Spezi final class AnalyticsModule: Module { @Dependency(Account.self) private var account func configure() { if account.signedIn { print("Resuming session for \(account.details?.userId ?? "unknown")") } } } ``` -------------------------------- ### Configure AccountService with External Storage Provider Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountService/Custom Storage Provider.md Use this initializer within your App's Configuration to set up an AccountStorageProvider with SpeziAccount. ```swift override var configuration: Configuration { Configuration { AccountConfiguration( service: SomeAccountService(), storageProvider: SomeStorageProvider(), configuration: [ // your configuration ... ] ) } } ``` -------------------------------- ### Managing AccountDetails Data Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Illustrates how to create, populate, read, merge, and validate `AccountDetails` objects. This collection stores all user-associated data and can be accessed using KeyPath syntax. Validation ensures data meets signup requirements. ```swift import SpeziAccount // Building AccountDetails (e.g., during sign-up or testing). var details = AccountDetails() details.accountId = "usr_abc123" details.userId = "leland@stanford.edu" details.name = PersonNameComponents(givenName: "Leland", familyName: "Stanford") details.genderIdentity = .male details.isNewUser = true // Reading values. if let name = details.name { print(name.formatted()) // "Leland Stanford" } print(details.userId) // "leland@stanford.edu" (falls back to accountId if not set) // Merging two AccountDetails collections. var base = AccountDetails() base.userId = "leland@stanford.edu" var extra = AccountDetails() extra.name = PersonNameComponents(givenName: "Leland", familyName: "Stanford") base.add(contentsOf: extra, merge: true) // Validating against the signup configuration. do { try details.validateAgainstSignupRequirements(account.configuration) } catch AccountOperationError.missingAccountValue(let keys) { print("Missing required keys: \(keys)") } ``` -------------------------------- ### Signup Form Interactions Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/XCTSpeziAccount/XCTSpeziAccount.docc/XCTSpeziAccount.md Utilities for interacting with the signup form, including filling fields, updating information, and closing the form. ```APIDOC ## Fill Signup Form ### Description Fills out the signup form with provided details. ### Method `fillSignupForm(email:password:name:genderIdentity:supplyDateOfBirth:)` ### Parameters - **email** (String) - The email address for the new account. - **password** (String) - The password for the new account. - **name** (String) - The full name of the user. - **genderIdentity** (String) - The user's gender identity. - **supplyDateOfBirth** (Bool) - Whether to supply the date of birth field. ``` ```APIDOC ## Update Gender Identity ### Description Updates the gender identity field within the signup form. ### Method `updateGenderIdentity(from:to:file:line:)` ### Parameters - **from** (String) - The current gender identity value. - **to** (String) - The new gender identity value. - **file** (String) - The file where the assertion is made. - **line** (Int) - The line number where the assertion is made. ``` ```APIDOC ## Change Date of Birth ### Description Navigates to or interacts with the date of birth selection within the signup form. ### Method `changeDateOfBirth()` ``` ```APIDOC ## Close Signup Form ### Description Closes the signup form, with an option to discard changes if prompted. ### Method `closeSignupForm(discardChangesIfAsked:)` ### Parameters - **discardChangesIfAsked** (Bool) - If true, discards changes without prompting if asked. ``` -------------------------------- ### Declare AccountKey with @AccountKey Macro Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Use the @AccountKey macro to generate conformance for account details. Register the key in AccountKeys and optionally provide custom entry and display views. ```swift import SpeziAccount import SpeziValidation import SpeziViews import SwiftUI // 1. Declare the key. extension AccountDetails { @AccountKey(name: "Biography", category: .personalDetails, as: String.self) var biography: String? } // 2. Register it in AccountKeys so it can be referenced by KeyPath. @KeyEntry(\. biography) extension AccountKeys {} // 3. Use it in AccountConfiguration. AccountConfiguration( service: MyAccountService(), configuration: [ .requires(\. userId), .requires(\. password), .supports(\. biography) ] ) // 4. Custom entry / display views (optional). private struct BiographyEntryView: DataEntryView { @Binding private var value: String var body: some View { VerifiableTextField("Enter your bio", text: $value) .autocorrectionDisabled() .lineLimit(2...5) } init(_ value: Binding) { self._value = value } } extension AccountDetails { @AccountKey( name: "Biography", category: .personalDetails, as: String.self, entryView: BiographyEntryView.self ) var biography: String? } ``` -------------------------------- ### ConfiguredAccountKey Requirements Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Defines requirement levels for account keys during sign-up and in the account overview. Use `.requires`, `.collects`, `.supports`, and `.manual` to control visibility and mandatory status. ```swift import SpeziAccount // .requires – must be provided at sign-up; mandatory in the signup form. // .collects – collected at sign-up but not strictly required. // .supports – not shown at sign-up; can be added/edited later in AccountOverview. // .manual – never shown in auto-generated UI; managed programmatically. let configuration: [ConfiguredAccountKey] = [ .requires(\.userId), // email field, always shown, signup fails without it .requires(\.password), // password field, always shown .requires(\.name), // full name, always shown .collects(\.dateOfBirth), // shown at signup but skippable .collects(\.genderIdentity), // shown at signup but skippable .supports(\.biography), // only available in AccountOverview edit mode .manual(\.internalFlag) // never rendered automatically ] AccountConfiguration( service: InMemoryAccountService(), configuration: configuration ) ``` -------------------------------- ### Implement AccountService Protocol Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Implement the AccountService protocol to integrate custom authentication platforms. Define supported keys, identity provider views, and handle login, logout, deletion, and updates. ```swift import Spezi import SpeziAccount public actor MyAccountService: AccountService { // Declare which keys the service can store natively. public let configuration = AccountServiceConfiguration(supportedKeys: .arbitrary) { UserIdConfiguration.emailAddress RequiredAccountKeys { \.userId \.password } } @Dependency(Account.self) private var account @Dependency(AccountNotifications.self) private var notifications // Identity provider views shown inside AccountSetup. @IdentityProvider(section: .primary) private var primaryProvider = EmailPasswordSetupView() public init() {} // Called by AccountSetup / SignupForm. public func login(userId: String, password: String) async throws { // authenticate against backend ... let details = try await fetchUserDetails(userId: userId) await account.supplyUserDetails(details) } public func logout() async throws { await account.removeUserDetails() } public func delete() async throws { guard let details = account.details else { return } // IMPORTANT: must emit deletingAccount before removing details. try await notifications.reportEvent(.deletingAccount(details.accountId)) await account.removeUserDetails() } public func updateAccountDetails(_ modifications: AccountModifications) async throws { // apply modifications to backend ... let updated = try await applyModifications(modifications) await account.supplyUserDetails(updated) } } ``` -------------------------------- ### Configure Account Service with SpeziAppDelegate Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/Setup Guides/Initial Setup.md Configure SpeziAccount by providing an AccountService and a list of AccountKeys with their requirement levels. This is typically done within your application's delegate. ```swift class MyAppDelegate: SpeziAppDelegate { override var configuration: Configuration { AccountConfiguration( service: InMemoryAccountService(), configuration: [ .requires(\.userId), .requires(\.password), .requires(\.name), .collects(\.dateOfBirth), .collects(\.genderIdentity) ] ) } } ``` -------------------------------- ### Implement Custom DataEntryView and DataDisplayView Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountKey/Adding new Account Values.md Create custom SwiftUI views for entering and displaying biography data. The EntryView disables auto-correction and allows multiple lines, while the DisplayView limits the displayed biography to three lines. ```swift import SpeziAccount import SpeziValidation import SpeziViews import SwiftUI /// A custom data entry view that disables auto-correction for the biography key. private struct EntryView: DataEntryView { @Binding private var biography: Value var body: some View { VerifiableTextField("enter biography", text: $biography) .autocorrectionDisabled() .lineLimit(2...5) } init(_ value: Binding) { self._biography = value } } /// A custom data display view that allows to display up to 3 lines of the biography. private struct DisplayView: DataDisplayView { private let value: String var body: some View { Text(value) .lineLimit(...3) // show biography in max 3 lines } init(_ value: String) { self.value = value } } // the updated @AccountKey macro definition from above extension AccountDetails { @AccountKey( name: "Biography", category: .personalDetails, as: String.self, displayView: DisplayView.self, entryView: EntryView.self ) var biography: String? } ``` -------------------------------- ### Configure Account Value for Phone Numbers Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccountPhoneNumbers/SpeziAccountPhoneNumbers.docc/SpeziAccountPhoneNumbers.md Add the phone numbers key to your account configuration to enable phone number support. ```swift var configuredValues: AccountValueConfiguration { [ // ... other keys ... .supports(.phoneNumbers) ] } ``` -------------------------------- ### Configuring AccountOverview for User Management Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Demonstrates embedding `AccountOverview` within a `NavigationStack` to allow users to view and edit their account details, change passwords, log out, or delete their account. Custom sections can be injected before the logout button. ```swift import SpeziAccount import SwiftUI struct SettingsView: View { var body: some View { NavigationStack { AccountOverview( close: .showCloseButton, // show an "X" toolbar button logout: .enabled, // enable the logout button deletion: .belowLogout // show a separate "Delete Account" button ) { // Inject extra sections between account fields and the logout button. NavigationLink { LicensesView() } label: { Label("License Information", systemImage: "doc.text") } NavigationLink { NotificationSettingsView() } label: { Label("Notifications", systemImage: "bell") } } } } } ``` -------------------------------- ### InMemoryAccountService for Previews and Tests Source: https://context7.com/stanfordspezi/speziaccount/llms.txt An in-memory implementation of AccountService suitable for SwiftUI Previews and UI tests, supporting various authentication methods. ```APIDOC ## InMemoryAccountService `InMemoryAccountService` is a fully-featured in-memory `AccountService` implementation for use in SwiftUI Previews and UI tests. It supports userId/password login, anonymous sign-in, Sign in with Apple (mock), and external storage integration. ```swift import SpeziAccount import SwiftUI // SwiftUI Preview with a pre-signed-in account. #Preview { var details = AccountDetails() details.userId = "preview@stanford.edu" details.name = PersonNameComponents(givenName: "Preview", familyName: "User") return NavigationStack { AccountOverview() } .previewWith { AccountConfiguration( service: InMemoryAccountService(), activeDetails: details ) } } // UI test: only enable email/password identity provider. struct TestApp: App { var body: some Scene { WindowGroup { ContentView() .spezi( AccountConfiguration( service: InMemoryAccountService(configure: .userIdPassword), configuration: [ .requires(.userId), .requires(.password), .requires(.name) ] ) ) } } } ``` ``` -------------------------------- ### Use InMemoryAccountService for Previews and UI Tests Source: https://context7.com/stanfordspezi/speziaccount/llms.txt An in-memory `AccountService` implementation supporting various login methods and external storage. Useful for SwiftUI Previews and UI tests. ```swift import SpeziAccount import SwiftUI // SwiftUI Preview with a pre-signed-in account. #Preview { var details = AccountDetails() details.userId = "preview@stanford.edu" details.name = PersonNameComponents(givenName: "Preview", familyName: "User") return NavigationStack { AccountOverview() } .previewWith { AccountConfiguration( service: InMemoryAccountService(), activeDetails: details ) } } ``` ```swift // UI test: only enable email/password identity provider. struct TestApp: App { var body: some Scene { WindowGroup { ContentView() .spezi( AccountConfiguration( service: InMemoryAccountService(configure: .userIdPassword), configuration: [ .requires(.userId), .requires(.password), .requires(.name) ] ) ) } } } ``` -------------------------------- ### Display Account Overview View Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/Setup Guides/Initial Setup.md Use the AccountOverview view to allow logged-in users to view or modify their account information. Ensure this view is only presented when a user account is active. ```swift struct MyView: View { var body: some View { NavigationStack { AccountOverview() } } } ``` -------------------------------- ### Add Phone Numbers Key to AccountConfiguration Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Integrates phone number account keys and verification flows by adding `.supports(\.phoneNumbers)` to `AccountConfiguration` and implementing the `PhoneVerificationConstraint` protocol. ```swift import SpeziAccount import SpeziAccountPhoneNumbers import Spezi import SwiftUI // 1. Add the phone numbers key to AccountConfiguration. class AppDelegate: SpeziAppDelegate { override var configuration: Configuration { Configuration(standard: MyStandard()) { AccountConfiguration( service: MyAccountService(), storageProvider: MyStorageProvider(), configuration: [ .requires(\.userId), .supports(\.phoneNumbers) ] ) PhoneVerificationProvider() } } } // 2. Conform your Standard to handle verification. actor MyStandard: Standard, PhoneVerificationConstraint { func startVerification(_ number: PhoneNumber) async throws { // Send an SMS code via Twilio / Firebase Phone Auth, etc. try await twilioClient.sendCode(to: number.numberString) } func completeVerification(_ number: PhoneNumber, _ code: String) async throws { // Validate the code and persist the verified number. try await twilioClient.verify(code, for: number.numberString) } func delete(_ number: PhoneNumber) async throws { try await removePhoneNumber(number) } } // 3. The AccountOverview will automatically surface PhoneNumbersDetailView // for the \.phoneNumbers key when it is configured. ``` -------------------------------- ### Login Functionality Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/XCTSpeziAccount/XCTSpeziAccount.docc/XCTSpeziAccount.md Provides methods for logging into the application using email or username credentials. ```APIDOC ## Login with Email ### Description Logs into the application using an email address and password. ### Method `login(email:password:)` ### Parameters - **email** (String) - The user's email address. - **password** (String) - The user's password. ### Request Example ```swift app.login(email: "user@example.com", password: "password123") ``` ``` ```APIDOC ## Login with Username ### Description Logs into the application using a username and password. ### Method `login(username:password:)` ### Parameters - **username** (String) - The user's username. - **password** (String) - The user's password. ### Request Example ```swift app.login(username: "testuser", password: "password123") ``` ``` -------------------------------- ### Add PhoneVerificationProvider to Configuration Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccountPhoneNumbers/SpeziAccountPhoneNumbers.docc/SpeziAccountPhoneNumbers.md Include the `PhoneVerificationProvider` in your app's configuration to enable phone number verification services. ```swift override var configuration: Configuration { Configuration(standard: YourStandard()) { AccountConfiguration( // ... other configuration ... ) PhoneVerificationProvider() } } ``` -------------------------------- ### Handle Account Notifications via Async Stream Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Subscribe to account lifecycle events using the `events` async stream from AccountNotifications. This allows any module to react to events like login, logout, or account deletion. ```swift import Spezi import SpeziAccount // --- Option A: Subscribe from any Module via async stream --- final class AuditLogModule: Module { @Dependency(AccountNotifications.self) private var notifications func configure() { Task { for await event in notifications.events { switch event { case .associatedAccount(let details): print("Login: \(details.userId)") case .detailsChanged(let old, let new): print("Details changed for \(old.userId)") case .disassociatingAccount(let details): print("Logout: \(details.userId)") case .deletingAccount(let accountId): print("Deleting account \(accountId)") } } } } } ``` -------------------------------- ### Register AccountKey with KeyEntry Source: https://github.com/stanfordspezi/speziaccount/blob/main/Sources/SpeziAccount/SpeziAccount.docc/AccountKey/Adding new Account Values.md Register your custom AccountKey with the AccountKeys collection using the @KeyEntry macro. This makes the AccountKey accessible within the SpeziAccount system. ```swift @KeyEntry(\ بایography) extension AccountKeys {} ``` -------------------------------- ### Handle Account Notifications by Conforming to AccountNotifyConstraint Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Conform your Standard actor to AccountNotifyConstraint to directly handle account lifecycle events. This is useful for performing user-specific data management tasks upon account changes. ```swift import Spezi import SpeziAccount // --- Option B: Conform your Standard --- actor MyStandard: Standard, AccountNotifyConstraint { func respondToEvent(_ event: AccountNotifications.Event) async { switch event { case .deletingAccount(let accountId): // delete user-specific Firestore documents, health data, etc. await deleteUserData(for: accountId) case .associatedAccount(let details): await createUserRecord(for: details) default: break } } } ``` -------------------------------- ### SecurityRelatedModifier for Re-authentication Source: https://context7.com/stanfordspezi/speziaccount/llms.txt Demonstrates how to use the SecurityRelatedModifier to automatically prompt the user for re-authentication before executing security-sensitive operations. ```APIDOC ## SecurityRelatedModifier Wrap security-sensitive operations (password change, account deletion) in `SecurityRelatedModifier` to inject a re-authentication prompt into the view hierarchy automatically. ```swift import Spezi import SpeziAccount import SwiftUI private struct ReauthModifier: ViewModifier { @State private var showPrompt = false var continuation: CheckedContinuation? func body(content: Content) -> some View { content.alert("Confirm Identity", isPresented: $showPrompt) { Button("Cancel", role: .cancel) { continuation?.resume(throwing: CancellationError()) } Button("Continue", role: .destructive) { continuation?.resume() } } } mutating func ensureAuthenticated() async throws { try await withCheckedThrowingContinuation { cont in self.continuation = cont showPrompt = true } } } actor SecureAccountService: AccountService { @SecurityRelatedModifier private var reauth = ReauthModifier() public func delete() async throws { // Suspend here until the user confirms the alert injected by @SecurityRelatedModifier. try await reauth.ensureAuthenticated() // Proceed with deletion. } } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.