generated from StanfordBDHG/SwiftPackageTemplate
-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial draft of the AccountValue system; some localization, Account …
…Summary Views
- Loading branch information
Showing
35 changed files
with
794 additions
and
238 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
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 |
---|---|---|
|
@@ -7,23 +7,58 @@ | |
// | ||
|
||
import AuthenticationServices | ||
import SpeziViews | ||
import SwiftUI | ||
|
||
struct AccountSetup: View { | ||
public enum Constants { | ||
static let outerHorizontalPadding: CGFloat = 16 // TODO use 32? | ||
static let innerHorizontalPadding: CGFloat = 16 // TODO use 32? | ||
static let maxFrameWidth: CGFloat = 450 | ||
} | ||
public enum Constants { | ||
static let outerHorizontalPadding: CGFloat = 16 // TODO use 32? | ||
static let innerHorizontalPadding: CGFloat = 16 // TODO use 32? | ||
static let maxFrameWidth: CGFloat = 450 | ||
} | ||
|
||
/// A view which provides the default titlte and subtitlte text. | ||
public struct DefaultHeader: View { // TODO rename! | ||
@EnvironmentObject | ||
var account: Account | ||
private var account: Account | ||
|
||
public var body: some View { | ||
// TODO provide customizable with AccountViewStyle! | ||
Text("ACCOUNT_WELCOME".localized(.module)) | ||
.font(.largeTitle) | ||
.bold() | ||
.multilineTextAlignment(.center) | ||
.padding(.bottom) | ||
.padding(.top, 30) | ||
|
||
Group { | ||
if !account.signedIn { | ||
Text("ACCOUNT_WELCOME_SUBTITLE".localized(.module)) | ||
} else { | ||
Text("ACCOUNT_WELCOME_SIGNED_IN_SUBTITLE".localized(.module)) | ||
} | ||
} | ||
.multilineTextAlignment(.center) | ||
} | ||
|
||
public init() {} | ||
} | ||
|
||
public struct AccountSetup<Header: View>: View { | ||
private let header: Header | ||
|
||
@EnvironmentObject var account: Account | ||
@Environment(\.colorScheme) | ||
var colorScheme | ||
|
||
var services: [any AccountService] { | ||
private var services: [any AccountService] { | ||
account.accountServices | ||
} | ||
|
||
var embeddableAccountService: (any EmbeddableAccountService)? { | ||
private var identityProviders: [String] { | ||
["Apple"] // TODO query from account and model them | ||
} | ||
|
||
private var embeddableAccountService: (any EmbeddableAccountService)? { | ||
let embeddableServices = services | ||
.filter { $0 is any EmbeddableAccountService } | ||
|
||
|
@@ -35,35 +70,35 @@ struct AccountSetup: View { | |
return nil | ||
} | ||
|
||
var nonEmbeddableAccountServices: [any AccountService] { | ||
private var nonEmbeddableAccountServices: [any AccountService] { | ||
services | ||
.filter { !($0 is any EmbeddableAccountService) } | ||
} | ||
|
||
@Environment(\.colorScheme) | ||
var colorScheme | ||
private var documentationUrl: URL { | ||
// we may move to a #URL macro once Swift 5.9 is shipping | ||
guard let docsUrl = URL(string: "https://swiftpackageindex.com/stanfordspezi/speziaccount/documentation/speziaccount/createanaccountservice") else { | ||
fatalError("Failed to construct SpeziAccount Documentation URL. Please review URL syntax!") | ||
} | ||
|
||
return docsUrl | ||
} | ||
|
||
var body: some View { | ||
public var body: some View { | ||
GeometryReader { proxy in | ||
ScrollView(.vertical) { | ||
VStack { | ||
// TODO draw account summary if we are already signed in! | ||
header | ||
|
||
Spacer() | ||
|
||
VStack { | ||
primaryAccountServicesReplacement | ||
|
||
// TODO show divider only if there is a least one account service AND identity provider! | ||
servicesDivider | ||
|
||
identityProviderButtons | ||
if let account = account.account { | ||
displayAccount(account: account) | ||
} else { | ||
noAccountState | ||
} | ||
.padding(.horizontal, Constants.innerHorizontalPadding) | ||
.frame(maxWidth: Constants.maxFrameWidth) // landscape optimizations | ||
// TODO for large dynamic size it would make sense to scale it though? | ||
|
||
// TODO provide ability to inject footer (e.g., terms and conditions?) | ||
Spacer() | ||
Spacer() | ||
Spacer() | ||
|
@@ -75,30 +110,43 @@ struct AccountSetup: View { | |
} | ||
} | ||
|
||
/// The Views Title and subtitle text. | ||
@ViewBuilder | ||
var header: some View { | ||
// TODO provide customizable with AccountViewStyle! | ||
Text("Welcome! 👋") // TODO localize | ||
.font(.largeTitle) | ||
.bold() | ||
.multilineTextAlignment(.center) | ||
.padding(.bottom) | ||
.padding(.top, 30) | ||
@ViewBuilder var noAccountState: some View { | ||
if services.isEmpty && identityProviders.isEmpty { | ||
showEmptyView | ||
} else { | ||
VStack { | ||
accountServicesSection | ||
|
||
Text("Please create an account to do whatever. You may create an account if you don't have one already!") // TODO localize! | ||
if !services.isEmpty && !identityProviders.isEmpty { | ||
servicesDivider | ||
} | ||
|
||
identityProviderSection | ||
} | ||
.padding(.horizontal, Constants.innerHorizontalPadding) | ||
.frame(maxWidth: Constants.maxFrameWidth) // landscape optimizations | ||
// TODO for large dynamic size it would make sense to scale it though? | ||
} | ||
} | ||
|
||
@ViewBuilder var showEmptyView: some View { | ||
Text("MISSING_ACCOUNT_SERVICES".localized(.module)) | ||
.multilineTextAlignment(.center) | ||
.foregroundColor(.secondary) | ||
|
||
Button(action: { | ||
UIApplication.shared.open(documentationUrl) | ||
}) { | ||
Text("OPEN_DOCUMENTATION".localized(.module)) | ||
} | ||
.padding() | ||
} | ||
|
||
@ViewBuilder | ||
var primaryAccountServicesReplacement: some View { | ||
if services.isEmpty { | ||
Text("Empty!! :(((") // TODO only place hint if there are not even identity providers! | ||
} else if let embeddableService = embeddableAccountService { | ||
@ViewBuilder var accountServicesSection: some View { | ||
if let embeddableService = embeddableAccountService { | ||
let embeddableViewStyle = embeddableService.viewStyle | ||
// TODO i can get back type erasure right? | ||
AnyView(embeddableViewStyle.makeEmbeddedAccountView()) | ||
// TODO inject account service!! lol, nothing is typed! | ||
|
||
|
||
if !nonEmbeddableAccountServices.isEmpty { | ||
|
@@ -135,20 +183,12 @@ struct AccountSetup: View { | |
|
||
// TODO may we provide a default implementation, or work with a optional serviceButton style? | ||
} | ||
/* | ||
Button(action: { | ||
print("Navigation?") | ||
}) { | ||
Text("Account service \(index)") // TODO we need a name? | ||
} | ||
*/ | ||
} | ||
} | ||
} | ||
|
||
// The "or" divider between primary account services and the third-party identity providers | ||
@ViewBuilder | ||
var servicesDivider: some View { | ||
@ViewBuilder var servicesDivider: some View { | ||
HStack { | ||
VStack { | ||
Divider() | ||
|
@@ -166,17 +206,17 @@ struct AccountSetup: View { | |
} | ||
|
||
/// VStack of buttons provided by the identity providers | ||
@ViewBuilder | ||
var identityProviderButtons: some View { | ||
@ViewBuilder var identityProviderSection: some View { | ||
VStack { | ||
SignInWithAppleButton { request in | ||
print("Sign in request!") | ||
} onCompletion: { result in | ||
print("sing in completed") | ||
} | ||
ForEach(identityProviders.indices, id: \.self) { index in | ||
SignInWithAppleButton { request in | ||
print("Sign in request!") | ||
} onCompletion: { result in | ||
print("sing in completed") | ||
} | ||
.frame(height: 55) | ||
|
||
.signInWithAppleButtonStyle(colorScheme == .light ? .black : .white) | ||
} | ||
} | ||
|
||
// TODO we want to check if there is a single username/password provider and the rest are identity providers! | ||
|
@@ -185,6 +225,19 @@ struct AccountSetup: View { | |
// => KeyPasswordBasedAuthentication[Service] | ||
// => IdentityProvideBasedAuthentication[Service] | ||
} | ||
|
||
// TODO docs | ||
public init(@ViewBuilder _ header: () -> Header = { DefaultHeader() }) { | ||
self.header = header() | ||
} | ||
|
||
func displayAccount(account: AccountValuesWhat) -> some View { | ||
let service = self.account.accountServices.first! // TODO how to get the primary account service! | ||
|
||
// TODO someone needs to place the Continue button? | ||
|
||
return AnyView(service.viewStyle.makeAccountSummary(account: account)) | ||
} | ||
} | ||
|
||
#if DEBUG | ||
|
@@ -203,13 +256,26 @@ struct AccountView_Previews: PreviewProvider { | |
] | ||
}() | ||
|
||
static let account1: AccountValuesWhat = AccountValueStorageBuilder() | ||
.add(UserIdAccountValueKey.self, value: "[email protected]") | ||
.add(NameAccountValueKey.self, value: PersonNameComponents(givenName: "Andreas", familyName: "Bauer")) | ||
.build() | ||
|
||
static var previews: some View { | ||
ForEach(accountServicePermutations.indices, id: \.self) { index in | ||
NavigationStack { | ||
AccountSetup() | ||
} | ||
.environmentObject(Account(accountServices: accountServicePermutations[index])) | ||
} | ||
|
||
NavigationStack { | ||
AccountSetup() | ||
} | ||
.environmentObject(Account( | ||
accountServices: [DefaultUsernamePasswordAccountService()], | ||
account: account1 | ||
)) | ||
} | ||
} | ||
#endif |
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
Oops, something went wrong.