Skip to content

Commit 224f19f

Browse files
Merge branch 'main' into license-headers
2 parents eaf069b + 1d8ff90 commit 224f19f

File tree

13 files changed

+448
-167
lines changed

13 files changed

+448
-167
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public protocol ExternalAuthProvider {
2222

2323
public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider {
2424
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
25+
@MainActor func deleteUser(user: User) async throws
2526
}
2627

2728
public protocol FacebookProviderAuthUIProtocol: ExternalAuthProvider {
@@ -97,6 +98,45 @@ public final class AuthService {
9798
public var authenticationFlow: AuthenticationFlow = .login
9899
public var errorMessage = ""
99100
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+
100140
private var unsafeGoogleProvider: (any GoogleProviderAuthUIProtocol)?
101141
private var unsafeFacebookProvider: (any FacebookProviderAuthUIProtocol)?
102142
private var unsafePhoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)?
@@ -160,6 +200,8 @@ public final class AuthService {
160200
}
161201
}
162202

203+
// MARK: - End Provider APIs
204+
163205
private func safeActionCodeSettings() throws -> ActionCodeSettings {
164206
// email sign-in requires action code settings
165207
guard let actionCodeSettings = configuration
@@ -241,7 +283,7 @@ public final class AuthService {
241283
try await handleAutoUpgradeAnonymousUser(credentials: credentials)
242284
} else {
243285
let result = try await auth.signIn(with: credentials)
244-
signedInCredential = result.credential
286+
signedInCredential = result.credential ?? credentials
245287
}
246288
updateAuthenticationState()
247289
} catch {
@@ -274,11 +316,13 @@ public extension AuthService {
274316
func deleteUser() async throws {
275317
do {
276318
if let user = auth.currentUser, let providerId = signedInCredential?.provider {
277-
if providerId == "password" {
319+
if providerId == EmailAuthProviderID {
278320
let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt)
279321
try await operation(on: user)
280-
} else if providerId == "facebook.com" {
322+
} else if providerId == FacebookAuthProviderID {
281323
try await facebookProvider.deleteUser(user: user)
324+
} else if providerId == GoogleAuthProviderID {
325+
try await googleProvider.deleteUser(user: user)
282326
}
283327
}
284328

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ public class StringUtils {
285285
/// found in:
286286
/// - SignInWithFacebookButton
287287
public var facebookLoginButtonLabel: String {
288-
return localizedString(for: "Continue with Facebook")
288+
return localizedString(for: "Sign in with Facebook")
289289
}
290290

291291
/// Facebook provider
@@ -333,8 +333,8 @@ public class StringUtils {
333333
/// Phone provider
334334
/// found in:
335335
/// - PhoneAuthButtonView
336-
public var smsCodeSentLabel: String {
337-
return localizedString(for: "SMS code sent")
336+
public var smsCodeSendButtonLabel: String {
337+
return localizedString(for: "Send SMS code")
338338
}
339339

340340
/// Phone provider
@@ -364,4 +364,11 @@ public class StringUtils {
364364
public var privacyPolicyLabel: String {
365365
return localizedString(for: "PrivacyPolicy")
366366
}
367+
368+
/// Alert Error title
369+
/// found in:
370+
/// PasswordRecoveryView
371+
public var alertErrorTitle: String {
372+
return localizedString(for: "Error")
373+
}
367374
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,99 @@ public struct AuthPickerView {
2525
authService.authenticationFlow = authService
2626
.authenticationFlow == .login ? .signUp : .login
2727
}
28-
}
2928

30-
extension AuthPickerView: View {
31-
public var body: some View {
32-
VStack {
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 {
3339
Text(authService.string.authPickerTitle)
3440
.font(.largeTitle)
3541
.fontWeight(.bold)
3642
.padding()
37-
if authService.authenticationState == .authenticated {
38-
SignedInView()
39-
} else if authService.authView == .passwordRecovery {
40-
PasswordRecoveryView()
41-
} else if authService.authView == .emailLink {
42-
EmailLinkView()
43-
} else {
44-
if authService.emailSignInEnabled {
45-
Text(authService.authenticationFlow == .login ? authService.string
46-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
47-
Divider()
48-
EmailAuthView()
43+
}
44+
}
45+
}
46+
47+
extension AuthPickerView: View {
48+
public var body: some View {
49+
ScrollView {
50+
VStack {
51+
authPickerTitleView
52+
if authService.authenticationState == .authenticated {
53+
SignedInView()
54+
} else {
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+
}
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()
93+
}
4994
}
50-
VStack {
51-
authService.renderButtons()
52-
}.padding(.horizontal)
53-
if authService.emailSignInEnabled {
54-
Divider()
95+
}.sheet(isPresented: isAuthModalPresented) {
96+
VStack(spacing: 0) {
5597
HStack {
56-
Text(authService
57-
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
58-
authService.string.alreadyHaveAnAccountLabel)
5998
Button(action: {
60-
withAnimation {
61-
switchFlow()
62-
}
99+
authService.dismissModal()
63100
}) {
64-
Text(authService.authenticationFlow == .signUp ? authService.string
65-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
66-
.fontWeight(.semibold)
67-
.foregroundColor(.blue)
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)
68108
}
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()
69120
}
70-
PrivacyTOCsView(displayMode: .footer)
71-
Text(authService.errorMessage).foregroundColor(.red)
72121
}
73122
}
74123
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ extension EmailAuthView: View {
137137
}
138138
}
139139
.disabled(!isValid)
140-
.padding([.top, .bottom], 8)
140+
.padding([.top, .bottom, .horizontal], 8)
141141
.frame(maxWidth: .infinity)
142142
.buttonStyle(.borderedProminent)
143143
Button(action: {

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PasswordPromptView.swift

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import FirebaseCore
1516
import SwiftUI
1617

1718
struct PasswordPromptSheet {
@@ -23,26 +24,45 @@ struct PasswordPromptSheet {
2324
extension PasswordPromptSheet: View {
2425
var body: some View {
2526
VStack(spacing: 20) {
26-
SecureField(authService.string.passwordInputLabel, text: $password)
27-
.textFieldStyle(.roundedBorder)
27+
Text(authService.string.confirmPasswordInputLabel)
28+
.font(.largeTitle)
29+
.fontWeight(.bold)
2830
.padding()
2931

30-
HStack {
31-
Button(authService.string.cancelButtonLabel) {
32-
coordinator.cancel()
33-
}
34-
Spacer()
35-
Button(authService.string.okButtonLabel) {
36-
coordinator.submit(password: password)
37-
}
38-
.disabled(password.isEmpty)
32+
Divider()
33+
34+
LabeledContent {
35+
TextField(authService.string.passwordInputLabel, text: $password)
36+
.textInputAutocapitalization(.never)
37+
.disableAutocorrection(true)
38+
.submitLabel(.next)
39+
} label: {
40+
Image(systemName: "lock")
41+
}.padding(.vertical, 10)
42+
.background(Divider(), alignment: .bottom)
43+
.padding(.bottom, 4)
44+
45+
Button(action: {
46+
coordinator.submit(password: password)
47+
}) {
48+
Text(authService.string.okButtonLabel)
49+
.padding(.vertical, 8)
50+
.frame(maxWidth: .infinity)
51+
}
52+
.disabled(password.isEmpty)
53+
.padding([.top, .bottom, .horizontal], 8)
54+
.frame(maxWidth: .infinity)
55+
.buttonStyle(.borderedProminent)
56+
57+
Button(authService.string.cancelButtonLabel) {
58+
coordinator.cancel()
3959
}
40-
.padding(.horizontal)
4160
}
4261
.padding()
4362
}
4463
}
4564

4665
#Preview {
47-
PasswordPromptSheet(coordinator: PasswordPromptCoordinator())
66+
FirebaseOptions.dummyConfigurationForPreview()
67+
return PasswordPromptSheet(coordinator: PasswordPromptCoordinator()).environment(AuthService())
4868
}

0 commit comments

Comments
 (0)