Skip to content

Move buttons out of ViewBuilder #1243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
@preconcurrency import FirebaseAuth
import SwiftUI

public protocol GoogleProviderProtocol {
func handleUrl(_ url: URL) -> Bool
public protocol ExternalAuthProvider {
associatedtype ButtonType: View
@MainActor var authButton: ButtonType { get }
}

public protocol GoogleProviderProtocol: ExternalAuthProvider {
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
}

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

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

Expand Down Expand Up @@ -61,9 +65,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: (any GoogleProviderProtocol)? = nil,
facebookProvider: (any FacebookProviderProtocol)? = nil,
phoneAuthProvider: (any PhoneAuthProviderProtocol)? = nil) {
self.auth = auth
self.configuration = configuration
self.googleProvider = googleProvider
Expand All @@ -84,12 +88,14 @@ public final class AuthService {
public var errorMessage = ""
public let passwordPrompt: PasswordPromptCoordinator = .init()

public var googleProvider: (any GoogleProviderProtocol)?
public var facebookProvider: (any FacebookProviderProtocol)?
public var phoneAuthProvider: (any PhoneAuthProviderProtocol)?

private var listenerManager: AuthListenerManager?
private let googleProvider: GoogleProviderProtocol?
private let facebookProvider: FacebookProviderProtocol?
private let phoneAuthProvider: PhoneAuthProviderProtocol?
private var signedInCredential: AuthCredential?

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

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

private var safePhoneAuthProvider: PhoneAuthProviderProtocol {
private var safePhoneAuthProvider: any PhoneAuthProviderProtocol {
get throws {
guard let provider = phoneAuthProvider else {
throw AuthServiceError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
},
"AuthPickerTitle" : {
"comment" : "Title for auth picker screen.",
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -404,6 +405,9 @@
}
}
}
},
"Enter Password" : {

},
"EnterYourEmail" : {
"comment" : "Title for email entry screen, email text field placeholder. Use short/abbreviated translation for 'email' which is less than 15 chars.",
Expand Down Expand Up @@ -932,6 +936,9 @@
}
}
}
},
"Submit" : {

},
"TermsOfService" : {
"comment" : "Text linked to a web page with the Terms of Service content.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import FacebookCore
import FacebookLogin
import FirebaseAuth
import FirebaseAuthSwiftUI
import SwiftUI

let kFacebookEmailScope = "email"
let kFacebookProfileScope = "public_profile"
Expand Down Expand Up @@ -34,6 +35,10 @@ public class FacebookProviderSwift: FacebookProviderProtocol {
shaNonce = CommonUtils.sha256Hash(of: rawNonce)
}

@MainActor public var authButton: SignInWithFacebookButton {
return SignInWithFacebookButton()
}

@MainActor public func signInWithFacebook(isLimitedLogin: Bool) async throws -> AuthCredential {
let trackingStatus = ATTrackingManager.trackingAuthorizationStatus
let tracking: LoginTracking = trackingStatus != .authorized ? .limited :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AuthService+Google.swift
// FirebaseUI
//
// Created by Morgan Chen on 4/16/25.
//

import FirebaseAuthSwiftUI

public extension AuthService {

@discardableResult
public func withGoogleSignIn() -> AuthService {
let clientID = auth.app?.options.clientID ?? ""
self.googleProvider = GoogleProviderSwift(clientID: clientID)
return self
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
@preconcurrency import FirebaseAuth
import FirebaseAuthSwiftUI
import FirebaseCore
import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

let kGoogleUserInfoEmailScope = "https://www.googleapis.com/auth/userinfo.email"
let kGoogleUserInfoProfileScope = "https://www.googleapis.com/auth/userinfo.profile"
Expand All @@ -16,12 +19,18 @@ public class GoogleProviderSwift: @preconcurrency GoogleProviderProtocol {
let scopes: [String]
let shortName = "Google"
let providerId = "google.com"
public init(scopes: [String]? = nil) {
let clientID: String
public init(scopes: [String]? = nil, clientID: String = FirebaseApp.app()!.options.clientID!) {
self.scopes = scopes ?? kDefaultScopes
self.clientID = clientID
}

public func handleUrl(_ url: URL) -> Bool {
return GIDSignIn.sharedInstance.handle(url)
@MainActor public var authButton: GoogleSignInButton {
return GoogleSignInButton {
Task {
try await self.signInWithGoogle(clientID: self.clientID)
}
}
}

@MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
@preconcurrency import FirebaseAuth
import FirebaseAuthSwiftUI
import SwiftUI

public typealias VerificationID = String

public class PhoneAuthProviderSwift: @preconcurrency PhoneAuthProviderProtocol {

public var authButton: Button<Text> {
// TODO: implement me
return Button("Phone", action: { })
}

public init() {}

@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ let package = Package(
dependencies: [
"FirebaseAuthSwiftUI",
"GoogleSignIn",
.product(name: "GoogleSignInSwift", package: "GoogleSignIn")
],
path: "FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
/* Begin PBXBuildFile section */
4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9B2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI */; };
4607CC9E2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9D2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI */; };
4670DEA72D9EA9E100E0D36A /* FirebaseFacebookSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4670DEA62D9EA9E100E0D36A /* FirebaseFacebookSwiftUI */; };
46C4EAB32DA801B200FC878B /* FirebasePhoneAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */; };
46CB7B252D773F2100F1FD0A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 46CB7B242D773F2100F1FD0A /* GoogleService-Info.plist */; };
46F89C392D64B04E000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46F89C382D64B04E000F8BC0 /* FirebaseAuthSwiftUI */; };
46F89C4D2D64BB9B000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46F89C4C2D64BB9B000F8BC0 /* FirebaseAuthSwiftUI */; };
8D808CB72DB0811900D2293F /* FirebaseFacebookSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8D808CB62DB0811900D2293F /* FirebaseFacebookSwiftUI */; };
8D808CB92DB081F900D2293F /* FirebasePhoneAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8D808CB82DB081F900D2293F /* FirebasePhoneAuthSwiftUI */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -76,11 +76,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4670DEA72D9EA9E100E0D36A /* FirebaseFacebookSwiftUI in Frameworks */,
8D808CB72DB0811900D2293F /* FirebaseFacebookSwiftUI in Frameworks */,
46F89C392D64B04E000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */,
46F89C4D2D64BB9B000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */,
4607CC9E2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI in Frameworks */,
46C4EAB32DA801B200FC878B /* FirebasePhoneAuthSwiftUI in Frameworks */,
8D808CB92DB081F900D2293F /* FirebasePhoneAuthSwiftUI in Frameworks */,
4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -155,8 +155,8 @@
46F89C4C2D64BB9B000F8BC0 /* FirebaseAuthSwiftUI */,
4607CC9B2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI */,
4607CC9D2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI */,
4670DEA62D9EA9E100E0D36A /* FirebaseFacebookSwiftUI */,
46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */,
8D808CB62DB0811900D2293F /* FirebaseFacebookSwiftUI */,
8D808CB82DB081F900D2293F /* FirebasePhoneAuthSwiftUI */,
);
productName = FirebaseSwiftUIExample;
productReference = 46F89C082D64A86C000F8BC0 /* FirebaseSwiftUIExample.app */;
Expand Down Expand Up @@ -241,7 +241,7 @@
mainGroup = 46F89BFF2D64A86C000F8BC0;
minimizedProjectReferenceProxies = 1;
packageReferences = (
4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */,
8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 46F89C092D64A86C000F8BC0 /* Products */;
Expand Down Expand Up @@ -617,9 +617,9 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */ = {
8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "../../../../firebaseUI-ios";
relativePath = "../../../../FirebaseUI-iOS";
};
/* End XCLocalSwiftPackageReference section */

Expand All @@ -632,16 +632,6 @@
isa = XCSwiftPackageProductDependency;
productName = FirebaseGoogleSwiftUI;
};
4670DEA62D9EA9E100E0D36A /* FirebaseFacebookSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */;
productName = FirebaseFacebookSwiftUI;
};
46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */;
productName = FirebasePhoneAuthSwiftUI;
};
46F89C382D64B04E000F8BC0 /* FirebaseAuthSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
productName = FirebaseAuthSwiftUI;
Expand All @@ -650,6 +640,16 @@
isa = XCSwiftPackageProductDependency;
productName = FirebaseAuthSwiftUI;
};
8D808CB62DB0811900D2293F /* FirebaseFacebookSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */;
productName = FirebaseFacebookSwiftUI;
};
8D808CB82DB081F900D2293F /* FirebasePhoneAuthSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */;
productName = FirebasePhoneAuthSwiftUI;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 46F89C002D64A86C000F8BC0 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import FirebaseCore
import FirebaseFacebookSwiftUI
import FirebaseGoogleSwiftUI
import FirebasePhoneAuthSwiftUI
import GoogleSignIn
import SwiftData
import SwiftUI

let googleProvider = GoogleProviderSwift()

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [
Expand Down Expand Up @@ -47,15 +46,17 @@ class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
ApplicationDelegate.shared.application(
if ApplicationDelegate.shared.application(
app,
open: url,
sourceApplication: options[UIApplication.OpenURLOptionsKey
.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation]
)
) {
return true
}

return googleProvider.handleUrl(url)
return GIDSignIn.sharedInstance.handle(url)
}
}

Expand Down Expand Up @@ -94,10 +95,12 @@ struct ContentView: View {
let phoneAuthProvider = PhoneAuthProviderSwift()
authService = AuthService(
configuration: configuration,
googleProvider: googleProvider,
googleProvider: nil,
facebookProvider: facebookProvider,
phoneAuthProvider: phoneAuthProvider
)
// Transition to this api
.withGoogleSignIn()
}

var body: some View {
Expand Down