Skip to content

Commit

Permalink
Upgrade to new Spezi and SpeziFoundation frameworks (#36)
Browse files Browse the repository at this point in the history
# Upgrade to new Spezi and SpeziFoundation frameworks

## ♻️ Current situation & Problem
This PR updates SpeziAccount to the latest changes of the `Spezi` and
`SpeziFoundation` frameworks. Additionally, we migrate away from
ObservableObject to the new Observable framework.


## ⚙️ Release Notes 
 
* Upgrade Spezi to `0.8.0`
* Move to `@Observable`.

## 📚 Documentation
--


## ✅ Testing
--
## 📝 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).
  • Loading branch information
Supereg authored Nov 9, 2023
1 parent 80bd6be commit e6b2670
Show file tree
Hide file tree
Showing 71 changed files with 429 additions and 173 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ let package = Package(
.library(name: "SpeziAccount", targets: ["SpeziAccount"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.7.2")),
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation.git", .upToNextMinor(from: "0.1.0")),
.package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.8.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziViews", .upToNextMinor(from: "0.6.1")),
.package(url: "https://github.com/StanfordBDHG/XCTRuntimeAssertions", .upToNextMinor(from: "0.2.5")),
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.4"))
Expand All @@ -30,6 +31,7 @@ let package = Package(
.target(
name: "SpeziAccount",
dependencies: [
.product(name: "SpeziFoundation", package: "SpeziFoundation"),
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziViews", package: "SpeziViews"),
.product(name: "SpeziPersonalInfo", package: "SpeziViews"),
Expand Down
13 changes: 8 additions & 5 deletions Sources/SpeziAccount/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ import SwiftUI
/// - ``init(services:configuration:)``
/// - ``init(_:configuration:)``
/// - ``init(building:active:configuration:)``
@Observable
@MainActor
public class Account: ObservableObject, Sendable {
public final class Account: Sendable {
private let logger: Logger

/// The `signedIn` property determines if the the current Account context is signed in or not yet signed in.
Expand All @@ -71,7 +72,7 @@ public class Account: ObservableObject, Sendable {
/// This has the following implications. When `signedIn` is `false`, there might still be a `details` instance present.
/// Similarly, when `details` is set to `nil, `signedIn` is guaranteed to be `false`. Otherwise,
/// if `details` is set to some value, the `signedIn` property might still be set to `false`.
@Published public private(set) var signedIn: Bool
public private(set) var signedIn: Bool

/// Provides access to associated data of the currently associated user account.
///
Expand All @@ -80,7 +81,7 @@ public class Account: ObservableObject, Sendable {
///
/// - Note: The associated ``AccountService`` that is responsible for managing the associated user can be retrieved
/// using the ``AccountDetails/accountService`` property.
@Published public private(set) var details: AccountDetails?
public private(set) var details: AccountDetails?

/// The user-defined configuration of account values that all user accounts need to support.
public let configuration: AccountValueConfiguration
Expand All @@ -103,8 +104,10 @@ public class Account: ObservableObject, Sendable {
) {
self.logger = LoggerKey.defaultValue

self._signedIn = Published(wrappedValue: details != nil)
self._details = Published(wrappedValue: details)
// we have to initialize the macro generated properties directly to stay non-isolated.
self._signedIn = details != nil
self._details = details

self.configuration = supportedConfiguration
self.registeredAccountServices = services

Expand Down
15 changes: 3 additions & 12 deletions Sources/SpeziAccount/AccountConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,22 @@ import XCTRuntimeAssertions
///
/// - Note: For more information on how to provide an ``AccountService`` if you are implementing your own Spezi `Component`
/// refer to the <doc:Creating-your-own-Account-Service> article.
public final class AccountConfiguration: Component, ObservableObjectProvider {
public final class AccountConfiguration: Module {
private let logger = LoggerKey.defaultValue

/// The user-defined configuration of account values that all user accounts need to support.
private let configuredAccountKeys: AccountValueConfiguration
/// An array of ``AccountService``s provided directly in the initializer of the configuration object.
private let providedAccountServices: [any AccountService]

private var account: Account?
@Model private var account: Account

@StandardActor private var standard: any Standard

/// The array of ``AccountService``s provided through other Spezi `Components`.
@Collect private var accountServices: [any AccountService]


public var observableObjects: [any ObservableObject] {
guard let account else {
preconditionFailure("Tried to access ObservableObjectProvider before \(Self.self).configure() was called")
}

return [account]
}


/// Initializes a `AccountConfiguration` without directly providing any ``AccountService`` instances.
///
/// ``AccountService`` instances might be automatically collected from other Spezi `Component`s that provide some.
Expand Down Expand Up @@ -93,7 +84,7 @@ public final class AccountConfiguration: Component, ObservableObjectProvider {
configuration: configuredAccountKeys
)

self.account?.injectWeakAccount(into: standard)
self.account.injectWeakAccount(into: standard)
}

private func verifyConfigurationRequirements(against service: any AccountService) -> any AccountService {
Expand Down
12 changes: 6 additions & 6 deletions Sources/SpeziAccount/AccountHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public struct AccountHeader: View {
// swiftlint:disable:previous attributes
}

@EnvironmentObject private var account: Account
@Environment(Account.self) private var account
private var caption: LocalizedStringResource

public var body: some View {
Expand Down Expand Up @@ -89,12 +89,12 @@ public struct AccountHeader: View {
.set(\.name, value: PersonNameComponents(givenName: "Andreas", familyName: "Bauer"))

return AccountHeader()
.environmentObject(Account(building: details, active: MockUserIdPasswordAccountService()))
.environment(Account(building: details, active: MockUserIdPasswordAccountService()))
}

#Preview {
AccountHeader(caption: "Email, Password, Preferences")
.environmentObject(Account(MockUserIdPasswordAccountService()))
.environment(Account(MockUserIdPasswordAccountService()))
}

#Preview {
Expand All @@ -113,7 +113,7 @@ public struct AccountHeader: View {
}
}
}
.environmentObject(Account(building: details, active: MockUserIdPasswordAccountService()))
.environment(Account(building: details, active: MockUserIdPasswordAccountService()))
}

#Preview {
Expand All @@ -131,7 +131,7 @@ public struct AccountHeader: View {
}
}
}
.environmentObject(Account(building: details, active: MockUserIdPasswordAccountService()))
.environment(Account(building: details, active: MockUserIdPasswordAccountService()))
}

#Preview {
Expand All @@ -146,6 +146,6 @@ public struct AccountHeader: View {
}
}
}
.environmentObject(Account(MockUserIdPasswordAccountService()))
.environment(Account(MockUserIdPasswordAccountService()))
}
#endif
8 changes: 4 additions & 4 deletions Sources/SpeziAccount/AccountOverview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import SwiftUI
/// current edit mode of the view. This can be helpful to, e.g., render a custom `Close` Button if the
/// view is not editing when presenting the AccountOverview in a sheet.
public struct AccountOverview<AdditionalSections: View>: View {
@EnvironmentObject private var account: Account
@Environment(Account.self) private var account

@Binding private var isEditing: Bool

Expand Down Expand Up @@ -126,12 +126,12 @@ struct AccountOverView_Previews: PreviewProvider {
}
}
}
.environmentObject(Account(building: details, active: MockUserIdPasswordAccountService()))
.environment(Account(building: details, active: MockUserIdPasswordAccountService()))

NavigationStack {
AccountOverview()
}
.environmentObject(Account())
.environment(Account())
}
}
#endif
1 change: 0 additions & 1 deletion Sources/SpeziAccount/AccountService/AccountService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SwiftUI


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

import Foundation
import Spezi
import SpeziFoundation
import SwiftUI


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation


/// A `KnowledgeSource` that implements a configuration option for ``AccountServiceConfiguration``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation
import SwiftUI


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

import Foundation
import Spezi
import SpeziFoundation


/// The localized name of an ``AccountService``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation
import SpeziValidation


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation


/// The collection of ``AccountKey``s that are required when using the associated ``AccountService``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation
import SwiftUI


Expand Down
15 changes: 8 additions & 7 deletions Sources/SpeziAccount/AccountSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ public enum _AccountSetupState: EnvironmentKey { // swiftlint:disable:this type_
/// }
/// }
/// ```
@MainActor
public struct AccountSetup<Header: View, Continue: View>: View {
private let setupCompleteClosure: (AccountDetails) -> Void
private let header: Header
private let continueButton: Continue

@EnvironmentObject var account: Account
@Environment(Account.self) var account

@State private var setupState: _AccountSetupState = .generic
@State private var followUpSheet = false
Expand Down Expand Up @@ -112,8 +113,8 @@ public struct AccountSetup<Header: View, Continue: View>: View {
.frame(maxWidth: .infinity)
}
}
.onReceive(account.$details) { details in
if let details, case .setupShown = setupState {
.onChange(of: account.signedIn) {
if let details = account.details, case .setupShown = setupState {
let missingKeys = account.configuration.missingRequiredKeys(for: details, includeCollected: details.isNewUser)

if missingKeys.isEmpty {
Expand Down Expand Up @@ -236,14 +237,14 @@ struct AccountView_Previews: PreviewProvider {
@MainActor static var previews: some View {
ForEach(accountServicePermutations.indices, id: \.self) { index in
AccountSetup()
.environmentObject(Account(services: accountServicePermutations[index] + [MockSignInWithAppleProvider()]))
.environment(Account(services: accountServicePermutations[index] + [MockSignInWithAppleProvider()]))
}

AccountSetup()
.environmentObject(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))
.environment(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))

AccountSetup(state: .setupShown)
.environmentObject(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))
.environment(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))

AccountSetup(continue: {
Button(action: {
Expand All @@ -254,7 +255,7 @@ struct AccountView_Previews: PreviewProvider {
})
.buttonStyle(.borderedProminent)
})
.environmentObject(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))
.environment(Account(building: detailsBuilder, active: MockUserIdPasswordAccountService()))
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ public protocol AccountSetupViewStyle {
/// The associated ``AccountService`` instance.
var service: Service { get }

/// A `ViewModifier` that is injected into views with security related operations.
///
/// This modifier is injected into views that expose security related operations like changing the password
/// or change the user identifier. It is guaranteed that this modifier is not injected twice into the same
/// view hierarchy.
///
/// - Note: It is advised to implement this as a computed property.
var securityRelatedViewModifier: any ViewModifier { get }


/// The button label in the list of account services for the ``AccountSetup`` view.
@ViewBuilder
func makeServiceButtonLabel() -> ButtonLabel
Expand All @@ -41,6 +51,12 @@ public protocol AccountSetupViewStyle {


extension AccountSetupViewStyle {
/// Default implementation that doesn't modify anything.
public var securityRelatedViewModifier: any ViewModifier {
NoopModifier()
}


/// Default service button label using the ``AccountServiceName`` and ``AccountServiceImage`` configurations.
public func makeServiceButtonLabel() -> some View {
Group {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziAccount/AccountValue/AccountAnchor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation


/// A `RepositoryAnchor` for ``AccountStorage``.
Expand Down
3 changes: 2 additions & 1 deletion Sources/SpeziAccount/AccountValue/AccountKey+Views.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation
import SwiftUI


Expand All @@ -21,6 +21,7 @@ extension AccountKey where Value: CustomLocalizedStringResourceConvertible {
}


@MainActor
extension AccountKey {
static func emptyDataEntryView<Values: AccountValues>(for values: Values.Type) -> AnyView {
AnyView(GeneralizedDataEntryView<DataEntry, Values>(initialValue: initialValue.value))
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziAccount/AccountValue/AccountKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation
import SwiftUI
import XCTRuntimeAssertions

Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziAccount/AccountValue/AccountStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation


/// A `ValueRepository` that stores `KnowledgeSource`s anchored to the ``AccountAnchor``.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziAccount/AccountValue/AccountValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziFoundation


/// An arbitrary collection of account values.
Expand Down
Loading

0 comments on commit e6b2670

Please sign in to comment.