Skip to content
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
@preconcurrency import FirebaseAuth
import SwiftUI

public protocol GoogleProviderProtocol {
public protocol GoogleProviderAuthUIProtocol {
func handleUrl(_ url: URL) -> Bool
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
}

public protocol FacebookProviderProtocol {
public protocol FacebookProviderAuthUIProtocol {
@MainActor func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential
}

public protocol PhoneAuthProviderProtocol {
public protocol PhoneAuthProviderAuthUIProtocol {
@MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String
}

Expand Down Expand Up @@ -61,9 +61,9 @@ private final class AuthListenerManager {
@Observable
public final class AuthService {
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
googleProvider: GoogleProviderProtocol? = nil,
facebookProvider: FacebookProviderProtocol? = nil,
phoneAuthProvider: PhoneAuthProviderProtocol? = nil) {
googleProvider: GoogleProviderAuthUIProtocol? = nil,
facebookProvider: FacebookProviderAuthUIProtocol? = nil,
phoneAuthProvider: PhoneAuthProviderAuthUIProtocol? = nil) {
self.auth = auth
self.configuration = configuration
self.googleProvider = googleProvider
Expand All @@ -85,11 +85,11 @@ public final class AuthService {
public let passwordPrompt: PasswordPromptCoordinator = .init()

private var listenerManager: AuthListenerManager?
private let googleProvider: GoogleProviderProtocol?
private let facebookProvider: FacebookProviderProtocol?
private let phoneAuthProvider: PhoneAuthProviderProtocol?
private let googleProvider: GoogleProviderAuthUIProtocol?
private let facebookProvider: FacebookProviderAuthUIProtocol?
private let phoneAuthProvider: PhoneAuthProviderAuthUIProtocol?

private var safeGoogleProvider: GoogleProviderProtocol {
private var safeGoogleProvider: GoogleProviderAuthUIProtocol {
get throws {
guard let provider = googleProvider else {
throw AuthServiceError
Expand All @@ -99,7 +99,7 @@ public final class AuthService {
}
}

private var safeFacebookProvider: FacebookProviderProtocol {
private var safeFacebookProvider: FacebookProviderAuthUIProtocol {
get throws {
guard let provider = facebookProvider else {
throw AuthServiceError
Expand All @@ -109,7 +109,7 @@ public final class AuthService {
}
}

private var safePhoneAuthProvider: PhoneAuthProviderProtocol {
private var safePhoneAuthProvider: PhoneAuthProviderAuthUIProtocol {
get throws {
guard let provider = phoneAuthProvider else {
throw AuthServiceError
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CommonCrypto
import FirebaseCore
import Foundation
import Security

Expand Down Expand Up @@ -46,4 +47,19 @@ public class CommonUtils {
}
return hash.map { String(format: "%02x", $0) }.joined()
}

public static func dummyConfigurationForPreview() {
guard FirebaseApp.app() == nil else { return }

let options = FirebaseOptions(
googleAppID: "1:123:ios:123abc456def7890",
gcmSenderID: "dummy"
)
options.apiKey = "dummy"
options.projectID = "dummy-project-id"
options.bundleID = Bundle.main.bundleIdentifier ?? "com.example.dummy"
options.clientID = "dummy-abc.apps.googleusercontent.com"

FirebaseApp.configure(options: options)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ extension AuthPickerView: View {
withAnimation {
switchFlow()
}
}) {
}, label: {
Text(authService.authenticationFlow == .signUp ? "Log in" : "Sign up")
.fontWeight(.semibold)
.foregroundColor(.blue)
}
})
}
Text(authService.errorMessage).foregroundColor(.red)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ extension EmailAuthView: View {
if authService.authenticationFlow == .login {
Button(action: {
authService.authView = .passwordRecovery
}) {
}, label: {
Text("Forgotten Password?")
}
})
}

if authService.authenticationFlow == .signUp {
Expand All @@ -108,7 +108,7 @@ extension EmailAuthView: View {
if authService.authenticationFlow == .login { await signInWithEmailPassword() }
else { await createUserWithEmailPassword() }
}
}) {
}, label: {
if authService.authenticationState != .authenticating {
Text(authService.authenticationFlow == .login ? "Log in with password" : "Sign up")
.padding(.vertical, 8)
Expand All @@ -119,16 +119,22 @@ extension EmailAuthView: View {
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
}
})
.disabled(!isValid)
.padding([.top, .bottom], 8)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
Button(action: {
authService.authView = .passwordRecovery
}) {
}, label: {
Text("Prefer Email link sign-in?")
}
})
}
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return EmailAuthView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ extension EmailLinkView: View {
await sendEmailLink()
authService.emailLink = email
}
}) {
}, label: {
Text("Send email sign-in link")
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
})
.disabled(!CommonUtils.isValidEmail(email))
.padding([.top, .bottom], 8)
.frame(maxWidth: .infinity)
Expand All @@ -66,11 +66,17 @@ extension EmailLinkView: View {
}
.navigationBarItems(leading: Button(action: {
authService.authView = .authPicker
}) {
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.blue)
Text("Back")
.foregroundColor(.blue)
})
}))
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return EmailLinkView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ extension PasswordPromptSheet: View {
.padding()
}
}

#Preview {
PasswordPromptSheet(coordinator: PasswordPromptCoordinator())
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ extension PasswordRecoveryView: View {
Task {
await sendPasswordRecoveryEmail()
}
}) {
}, label: {
Text("Password Recovery")
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
})
.disabled(!CommonUtils.isValidEmail(email))
.padding([.top, .bottom], 8)
.frame(maxWidth: .infinity)
Expand All @@ -59,11 +59,17 @@ extension PasswordRecoveryView: View {
}
.navigationBarItems(leading: Button(action: {
authService.authView = .authPicker
}) {
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.blue)
Text("Back")
.foregroundColor(.blue)
})
}))
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return PasswordRecoveryView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ extension SignedInView: View {
}
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return SignedInView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ extension VerifyEmailView: View {
Task {
await sendEmailVerification()
}
}) {
}, label: {
Text("Verify email address?")
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
})
.padding([.top, .bottom], 8)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
Expand All @@ -42,3 +42,9 @@ extension VerifyEmailView: View {
}
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return VerifyEmailView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public enum FacebookProviderError: Error {
case authenticationToken(String)
}

public class FacebookProviderSwift: FacebookProviderProtocol {
public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol {
let scopes: [String]
let shortName = "Facebook"
let providerId = "facebook.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extension SignInWithFacebookButton: View {
}
}
}
}) {
}, label: {
HStack {
Image(systemName: "f.circle.fill")
.font(.title)
Expand All @@ -73,7 +73,7 @@ extension SignInWithFacebookButton: View {
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
})
.alert(isPresented: $showCanceledAlert) {
Alert(
title: Text("Facebook login cancelled"),
Expand Down Expand Up @@ -108,3 +108,9 @@ extension SignInWithFacebookButton: View {
Text(errorMessage).foregroundColor(.red)
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return SignInWithFacebookButton()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public enum GoogleProviderError: Error {
case user(String)
}

public class GoogleProviderSwift: @preconcurrency GoogleProviderProtocol {
public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol {
let scopes: [String]
let shortName = "Google"
let providerId = "google.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension SignInWithGoogleButton: View {
Task {
try await signInWithGoogle()
}
}) {
}, label: {
if authService.authenticationState != .authenticating {
HStack {
Image(systemName: "globe") // Placeholder for Google logo
Expand All @@ -46,6 +46,12 @@ extension SignInWithGoogleButton: View {
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
}
})
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return SignInWithGoogleButton()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import FirebaseAuthSwiftUI

public typealias VerificationID = String

public class PhoneAuthProviderSwift: @preconcurrency PhoneAuthProviderProtocol {
public class PhoneAuthProviderAuthUI: @preconcurrency PhoneAuthProviderAuthUIProtocol {
public init() {}

@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ extension PhoneAuthButtonView: View {
)
}
}
}) {
}, label: {
Text("Send SMS code")
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
})
.disabled(!PhoneUtils.isValidPhoneNumber(phoneNumber))
.padding([.top, .bottom], 8)
.frame(maxWidth: .infinity)
Expand All @@ -69,15 +69,15 @@ extension PhoneAuthButtonView: View {
}
showVerificationCodeInput = false
}
}) {
}, label: {
Text("Verify phone number and sign-in")
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(8)
.padding(.horizontal)
}
})
}.onOpenURL { url in
authService.auth.canHandle(url)
}
Expand All @@ -90,3 +90,9 @@ extension PhoneAuthButtonView: View {
Text(errorMessage).foregroundColor(.red)
}
}

#Preview {
CommonUtils.dummyConfigurationForPreview()
return PhoneAuthButtonView()
.environment(AuthService())
}
Loading