diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift index 69e3536e14..c26fed3635 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift @@ -2,8 +2,8 @@ import SwiftUI public protocol ExternalAuthProvider { - associatedtype ButtonType: View - @MainActor var authButton: ButtonType { get } + var id: String { get } + @MainActor func authButton() -> AnyView } public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider { @@ -65,15 +65,9 @@ private final class AuthListenerManager { @MainActor @Observable public final class AuthService { - public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(), - googleProvider: (any GoogleProviderAuthUIProtocol)? = nil, - facebookProvider: (any FacebookProviderAuthUIProtocol)? = nil, - phoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)? = nil) { + public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth()) { self.auth = auth self.configuration = configuration - self.googleProvider = googleProvider - self.facebookProvider = facebookProvider - self.phoneAuthProvider = phoneAuthProvider string = StringUtils(bundle: configuration.customStringsBundle ?? Bundle.module) listenerManager = AuthListenerManager(auth: auth, authEnvironment: self) } @@ -96,6 +90,21 @@ public final class AuthService { private var listenerManager: AuthListenerManager? private var signedInCredential: AuthCredential? + private var providers: [ExternalAuthProvider] = [] + public func register(provider: ExternalAuthProvider) { + providers.append(provider) + } + + public func renderButtons(spacing: CGFloat = 16) -> AnyView { + AnyView( + VStack(spacing: spacing) { + ForEach(providers, id: \.id) { provider in + provider.authButton() + } + } + ) + } + private var safeGoogleProvider: any GoogleProviderAuthUIProtocol { get throws { guard let provider = googleProvider else { diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Strings/Localizable.xcstrings b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Strings/Localizable.xcstrings index b7e9918b39..55ffb43c9f 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Strings/Localizable.xcstrings +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Strings/Localizable.xcstrings @@ -1047,6 +1047,9 @@ } } } + }, + "Update password" : { + }, "UpdateEmailAlertMessage" : { "comment" : "Alert action message shown before updating email action. Use short/abbreviated translation for 'email' which is less than 15 chars.", diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift index 517149ce98..0d1d84fbac 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift @@ -28,7 +28,7 @@ extension AuthPickerView: View { Text(authService.authenticationFlow == .login ? "Login" : "Sign up") VStack { Divider() } EmailAuthView() - providerButtons() + authService.renderButtons() VStack { Divider() } HStack { Text(authService diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AuthService+Facebook.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AuthService+Facebook.swift new file mode 100644 index 0000000000..f2cd364da2 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/AuthService+Facebook.swift @@ -0,0 +1,17 @@ +// +// AuthService+Facebook.swift +// FirebaseUI +// +// Created by Russell Wheatley on 01/05/2025. +// + +import FirebaseAuthSwiftUI + +public extension AuthService { + @discardableResult + func withFacebookSignIn(scopes scopes: [String]? = nil) -> AuthService { + facebookProvider = FacebookProviderAuthUI(scopes: scopes) + register(provider: facebookProvider!) + return self + } +} diff --git a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift index c6c425aec4..46152a37b6 100644 --- a/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift @@ -22,6 +22,7 @@ public enum FacebookProviderError: Error { } public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol { + public let id: String = "facebook" let scopes: [String] let shortName = "Facebook" let providerId = "facebook.com" @@ -35,8 +36,8 @@ public class FacebookProviderAuthUI: FacebookProviderAuthUIProtocol { shaNonce = CommonUtils.sha256Hash(of: rawNonce) } - @MainActor public var authButton: SignInWithFacebookButton { - return SignInWithFacebookButton() + @MainActor public func authButton() -> AnyView { + AnyView(SignInWithFacebookButton()) } @MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential { diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AuthService+Google.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AuthService+Google.swift index 584abba8e3..b7c9abc21a 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AuthService+Google.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/AuthService+Google.swift @@ -9,9 +9,10 @@ import FirebaseAuthSwiftUI public extension AuthService { @discardableResult - func withGoogleSignIn() -> AuthService { + func withGoogleSignIn(scopes scopes: [String]? = nil) -> AuthService { let clientID = auth.app?.options.clientID ?? "" - googleProvider = GoogleProviderAuthUI(clientID: clientID) + googleProvider = GoogleProviderAuthUI(scopes: scopes, clientID: clientID) + register(provider: googleProvider!) return self } } diff --git a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift index dce5b8ddf8..0879fea4c7 100644 --- a/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Services/GoogleProviderAuthUI.swift @@ -16,6 +16,7 @@ public enum GoogleProviderError: Error { } public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol { + public let id: String = "google" let scopes: [String] let shortName = "Google" let providerId = "google.com" @@ -25,12 +26,12 @@ public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol self.clientID = clientID } - @MainActor public var authButton: GoogleSignInButton { - return GoogleSignInButton { + @MainActor public func authButton() -> AnyView { + AnyView(GoogleSignInButton { Task { try await self.signInWithGoogle(clientID: self.clientID) } - } + }) } @MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential { diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/AuthService+Phone.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/AuthService+Phone.swift new file mode 100644 index 0000000000..2b8b34d77e --- /dev/null +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/AuthService+Phone.swift @@ -0,0 +1,17 @@ +// +// AuthService+Phone.swift +// FirebaseUI +// +// Created by Russell Wheatley on 09/05/2025. +// + +import FirebaseAuthSwiftUI + +public extension AuthService { + @discardableResult + func withPhoneSignIn() -> AuthService { + phoneAuthProvider = PhoneAuthProviderAuthUI() + register(provider: phoneAuthProvider!) + return self + } +} diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index 39dbc4f1c5..f8c92608a3 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -5,12 +5,11 @@ import SwiftUI public typealias VerificationID = String public class PhoneAuthProviderAuthUI: @preconcurrency PhoneAuthProviderAuthUIProtocol { - public var authButton: Button { - // TODO: implement me - return Button("Phone", action: {}) - } + public let id: String = "phone" - public init() {} + @MainActor public func authButton() -> AnyView { + AnyView(PhoneAuthButtonView()) + } @MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID { return try await withCheckedThrowingContinuation { continuation in diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift index b25142ca81..9547de0308 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift @@ -28,20 +28,16 @@ struct ContentView: View { shouldAutoUpgradeAnonymousUsers: true, emailLinkSignInActionCodeSettings: actionCodeSettings ) - let facebookProvider = FacebookProviderAuthUI() - let phoneAuthProvider = PhoneAuthProviderAuthUI() authService = AuthService( - configuration: configuration, - facebookProvider: facebookProvider, - phoneAuthProvider: phoneAuthProvider + configuration: configuration ) .withGoogleSignIn() + .withFacebookSignIn() + .withPhoneSignIn() } var body: some View { AuthPickerView { - SignInWithGoogleButton() - SignInWithFacebookButton() PhoneAuthButtonView() }.environment(authService) } diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift index a0ef1c7f1e..de3be36562 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/FirebaseSwiftUIExampleApp.swift @@ -7,10 +7,7 @@ import FacebookCore import FirebaseAuth import FirebaseCore -import FirebaseGoogleSwiftUI -import FirebasePhoneAuthSwiftUI import GoogleSignIn -import SwiftData import SwiftUI class AppDelegate: NSObject, UIApplicationDelegate { @@ -61,8 +58,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { struct FirebaseSwiftUIExampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - init() {} - var body: some Scene { WindowGroup { NavigationView {