Skip to content

Commit 758fe3f

Browse files
Merge branch 'main' into swiftui-tests
2 parents 69b4901 + 616f3f4 commit 758fe3f

37 files changed

+967
-169
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/AuthServiceError.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115

216
import FirebaseAuth
317
import SwiftUI

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
@preconcurrency import FirebaseAuth
216
import Observation
317

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
import AuthenticationServices
216
import FirebaseAuth
317

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
import FirebaseAuth
216
import Foundation
317

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
@preconcurrency import FirebaseAuth
216
import SwiftUI
317

@@ -8,6 +22,7 @@ public protocol ExternalAuthProvider {
822

923
public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider {
1024
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
25+
@MainActor func deleteUser(user: User) async throws
1126
}
1227

1328
public protocol FacebookProviderAuthUIProtocol: ExternalAuthProvider {
@@ -83,6 +98,45 @@ public final class AuthService {
8398
public var authenticationFlow: AuthenticationFlow = .login
8499
public var errorMessage = ""
85100
public let passwordPrompt: PasswordPromptCoordinator = .init()
101+
102+
// MARK: - AuthPickerView Modal APIs
103+
104+
public var isShowingAuthModal = false
105+
106+
public enum AuthModalContentType {
107+
case phoneAuth
108+
}
109+
110+
public var currentModal: AuthModalContentType?
111+
112+
public var authModalViewBuilderRegistry: [AuthModalContentType: () -> AnyView] = [:]
113+
114+
public func registerModalView(for type: AuthModalContentType,
115+
@ViewBuilder builder: @escaping () -> AnyView) {
116+
authModalViewBuilderRegistry[type] = builder
117+
}
118+
119+
public func viewForCurrentModal() -> AnyView? {
120+
guard let type = currentModal,
121+
let builder = authModalViewBuilderRegistry[type] else {
122+
return nil
123+
}
124+
return builder()
125+
}
126+
127+
public func presentModal(for type: AuthModalContentType) {
128+
currentModal = type
129+
isShowingAuthModal = true
130+
}
131+
132+
public func dismissModal() {
133+
isShowingAuthModal = false
134+
}
135+
136+
// MARK: - End AuthPickerView Modal APIs
137+
138+
// MARK: - Provider APIs
139+
86140
private var unsafeGoogleProvider: (any GoogleProviderAuthUIProtocol)?
87141
private var unsafeFacebookProvider: (any FacebookProviderAuthUIProtocol)?
88142
private var unsafePhoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)?
@@ -146,6 +200,8 @@ public final class AuthService {
146200
}
147201
}
148202

203+
// MARK: - End Provider APIs
204+
149205
private func safeActionCodeSettings() throws -> ActionCodeSettings {
150206
// email sign-in requires action code settings
151207
guard let actionCodeSettings = configuration
@@ -227,7 +283,7 @@ public final class AuthService {
227283
try await handleAutoUpgradeAnonymousUser(credentials: credentials)
228284
} else {
229285
let result = try await auth.signIn(with: credentials)
230-
signedInCredential = result.credential
286+
signedInCredential = result.credential ?? credentials
231287
}
232288
updateAuthenticationState()
233289
} catch {
@@ -260,11 +316,13 @@ public extension AuthService {
260316
func deleteUser() async throws {
261317
do {
262318
if let user = auth.currentUser, let providerId = signedInCredential?.provider {
263-
if providerId == "password" {
319+
if providerId == EmailAuthProviderID {
264320
let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt)
265321
try await operation(on: user)
266-
} else if providerId == "facebook.com" {
322+
} else if providerId == FacebookAuthProviderID {
267323
try await facebookProvider.deleteUser(user: user)
324+
} else if providerId == GoogleAuthProviderID {
325+
try await googleProvider.deleteUser(user: user)
268326
}
269327
}
270328

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/CommonUtils.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
import CommonCrypto
216
import FirebaseCore
317
import Foundation

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
import FirebaseAuth
216
import SwiftUI
317

@@ -271,7 +285,7 @@ public class StringUtils {
271285
/// found in:
272286
/// - SignInWithFacebookButton
273287
public var facebookLoginButtonLabel: String {
274-
return localizedString(for: "Continue with Facebook")
288+
return localizedString(for: "Sign in with Facebook")
275289
}
276290

277291
/// Facebook provider
@@ -319,8 +333,8 @@ public class StringUtils {
319333
/// Phone provider
320334
/// found in:
321335
/// - PhoneAuthButtonView
322-
public var smsCodeSentLabel: String {
323-
return localizedString(for: "SMS code sent")
336+
public var smsCodeSendButtonLabel: String {
337+
return localizedString(for: "Send SMS code")
324338
}
325339

326340
/// Phone provider
@@ -350,4 +364,11 @@ public class StringUtils {
350364
public var privacyPolicyLabel: String {
351365
return localizedString(for: "PrivacyPolicy")
352366
}
367+
368+
/// Alert Error title
369+
/// found in:
370+
/// PasswordRecoveryView
371+
public var alertErrorTitle: String {
372+
return localizedString(for: "Error")
373+
}
353374
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
import FirebaseCore
216
import SwiftUI
317

@@ -11,52 +25,99 @@ public struct AuthPickerView {
1125
authService.authenticationFlow = authService
1226
.authenticationFlow == .login ? .signUp : .login
1327
}
28+
29+
private var isAuthModalPresented: Binding<Bool> {
30+
Binding(
31+
get: { authService.isShowingAuthModal },
32+
set: { authService.isShowingAuthModal = $0 }
33+
)
34+
}
35+
36+
@ViewBuilder
37+
private var authPickerTitleView: some View {
38+
if authService.authView == .authPicker {
39+
Text(authService.string.authPickerTitle)
40+
.font(.largeTitle)
41+
.fontWeight(.bold)
42+
.padding()
43+
}
44+
}
1445
}
1546

1647
extension AuthPickerView: View {
1748
public var body: some View {
18-
VStack {
19-
ScrollView {
20-
Text(authService.string.authPickerTitle)
21-
.font(.largeTitle)
22-
.fontWeight(.bold)
23-
.padding()
49+
ScrollView {
50+
VStack {
51+
authPickerTitleView
2452
if authService.authenticationState == .authenticated {
2553
SignedInView()
26-
} else if authService.authView == .passwordRecovery {
27-
PasswordRecoveryView()
28-
} else if authService.authView == .emailLink {
29-
EmailLinkView()
3054
} else {
31-
if authService.emailSignInEnabled {
32-
Text(authService.authenticationFlow == .login ? authService.string
33-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
34-
Divider()
35-
EmailAuthView()
55+
switch authService.authView {
56+
case .passwordRecovery:
57+
PasswordRecoveryView()
58+
case .emailLink:
59+
EmailLinkView()
60+
case .authPicker:
61+
if authService.emailSignInEnabled {
62+
Text(authService.authenticationFlow == .login ? authService.string
63+
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
64+
Divider()
65+
EmailAuthView()
66+
}
67+
VStack {
68+
authService.renderButtons()
69+
}.padding(.horizontal)
70+
if authService.emailSignInEnabled {
71+
Divider()
72+
HStack {
73+
Text(authService
74+
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
75+
authService.string.alreadyHaveAnAccountLabel)
76+
Button(action: {
77+
withAnimation {
78+
switchFlow()
79+
}
80+
}) {
81+
Text(authService.authenticationFlow == .signUp ? authService.string
82+
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
83+
.fontWeight(.semibold)
84+
.foregroundColor(.blue)
85+
}.accessibilityIdentifier("switch-auth-flow")
86+
}
87+
}
88+
PrivacyTOCsView(displayMode: .footer)
89+
Text(authService.errorMessage).foregroundColor(.red)
90+
default:
91+
// TODO: - possibly refactor this, see: https://github.com/firebase/FirebaseUI-iOS/pull/1259#discussion_r2105473437
92+
EmptyView()
3693
}
37-
VStack {
38-
authService.renderButtons()
39-
}.padding(.horizontal)
40-
if authService.emailSignInEnabled {
41-
Divider()
42-
HStack {
43-
Text(authService
44-
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
45-
authService.string.alreadyHaveAnAccountLabel)
46-
Button(action: {
47-
withAnimation {
48-
switchFlow()
49-
}
50-
}) {
51-
Text(authService.authenticationFlow == .signUp ? authService.string
52-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
53-
.fontWeight(.semibold)
54-
.foregroundColor(.blue)
55-
}.accessibilityIdentifier("switch-auth-flow")
94+
}
95+
}.sheet(isPresented: isAuthModalPresented) {
96+
VStack(spacing: 0) {
97+
HStack {
98+
Button(action: {
99+
authService.dismissModal()
100+
}) {
101+
HStack(spacing: 4) {
102+
Image(systemName: "chevron.left")
103+
.font(.system(size: 17, weight: .medium))
104+
Text(authService.string.backButtonLabel)
105+
.font(.system(size: 17))
106+
}
107+
.foregroundColor(.blue)
56108
}
109+
Spacer()
110+
}
111+
.padding()
112+
.background(Color(.systemBackground))
113+
114+
Divider()
115+
116+
if let view = authService.viewForCurrentModal() {
117+
view
118+
.frame(maxWidth: .infinity, maxHeight: .infinity)
119+
.padding()
57120
}
58-
PrivacyTOCsView(displayMode: .footer)
59-
Text(authService.errorMessage).foregroundColor(.red)
60121
}
61122
}
62123
}

0 commit comments

Comments
 (0)