Skip to content

Commit a2ea416

Browse files
Merge branch 'main' into reauthenticate-google
2 parents f991f96 + cb015e4 commit a2ea416

File tree

10 files changed

+339
-150
lines changed

10 files changed

+339
-150
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,45 @@ public final class AuthService {
8484
public var authenticationFlow: AuthenticationFlow = .login
8585
public var errorMessage = ""
8686
public let passwordPrompt: PasswordPromptCoordinator = .init()
87+
88+
// MARK: - AuthPickerView Modal APIs
89+
90+
public var isShowingAuthModal = false
91+
92+
public enum AuthModalContentType {
93+
case phoneAuth
94+
}
95+
96+
public var currentModal: AuthModalContentType?
97+
98+
public var authModalViewBuilderRegistry: [AuthModalContentType: () -> AnyView] = [:]
99+
100+
public func registerModalView(for type: AuthModalContentType,
101+
@ViewBuilder builder: @escaping () -> AnyView) {
102+
authModalViewBuilderRegistry[type] = builder
103+
}
104+
105+
public func viewForCurrentModal() -> AnyView? {
106+
guard let type = currentModal,
107+
let builder = authModalViewBuilderRegistry[type] else {
108+
return nil
109+
}
110+
return builder()
111+
}
112+
113+
public func presentModal(for type: AuthModalContentType) {
114+
currentModal = type
115+
isShowingAuthModal = true
116+
}
117+
118+
public func dismissModal() {
119+
isShowingAuthModal = false
120+
}
121+
122+
// MARK: - End AuthPickerView Modal APIs
123+
124+
// MARK: - Provider APIs
125+
87126
private var unsafeGoogleProvider: (any GoogleProviderAuthUIProtocol)?
88127
private var unsafeFacebookProvider: (any FacebookProviderAuthUIProtocol)?
89128
private var unsafePhoneAuthProvider: (any PhoneAuthProviderAuthUIProtocol)?
@@ -147,6 +186,8 @@ public final class AuthService {
147186
}
148187
}
149188

189+
// MARK: - End Provider APIs
190+
150191
private func safeActionCodeSettings() throws -> ActionCodeSettings {
151192
// email sign-in requires action code settings
152193
guard let actionCodeSettings = configuration

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public class StringUtils {
271271
/// found in:
272272
/// - SignInWithFacebookButton
273273
public var facebookLoginButtonLabel: String {
274-
return localizedString(for: "Continue with Facebook")
274+
return localizedString(for: "Sign in with Facebook")
275275
}
276276

277277
/// Facebook provider
@@ -319,8 +319,8 @@ public class StringUtils {
319319
/// Phone provider
320320
/// found in:
321321
/// - PhoneAuthButtonView
322-
public var smsCodeSentLabel: String {
323-
return localizedString(for: "SMS code sent")
322+
public var smsCodeSendButtonLabel: String {
323+
return localizedString(for: "Send SMS code")
324324
}
325325

326326
/// Phone provider
@@ -350,4 +350,11 @@ public class StringUtils {
350350
public var privacyPolicyLabel: String {
351351
return localizedString(for: "PrivacyPolicy")
352352
}
353+
354+
/// Alert Error title
355+
/// found in:
356+
/// PasswordRecoveryView
357+
public var alertErrorTitle: String {
358+
return localizedString(for: "Error")
359+
}
353360
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,51 +11,98 @@ public struct AuthPickerView {
1111
authService.authenticationFlow = authService
1212
.authenticationFlow == .login ? .signUp : .login
1313
}
14+
15+
private var isAuthModalPresented: Binding<Bool> {
16+
Binding(
17+
get: { authService.isShowingAuthModal },
18+
set: { authService.isShowingAuthModal = $0 }
19+
)
20+
}
21+
22+
@ViewBuilder
23+
private var authPickerTitleView: some View {
24+
if authService.authView == .authPicker {
25+
Text(authService.string.authPickerTitle)
26+
.font(.largeTitle)
27+
.fontWeight(.bold)
28+
.padding()
29+
}
30+
}
1431
}
1532

1633
extension AuthPickerView: View {
1734
public var body: some View {
1835
ScrollView {
1936
VStack {
20-
Text(authService.string.authPickerTitle)
21-
.font(.largeTitle)
22-
.fontWeight(.bold)
23-
.padding()
37+
authPickerTitleView
2438
if authService.authenticationState == .authenticated {
2539
SignedInView()
26-
} else if authService.authView == .passwordRecovery {
27-
PasswordRecoveryView()
28-
} else if authService.authView == .emailLink {
29-
EmailLinkView()
3040
} else {
31-
if authService.emailSignInEnabled {
32-
Text(authService.authenticationFlow == .login ? authService.string
33-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
34-
Divider()
35-
EmailAuthView()
36-
}
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()
41+
switch authService.authView {
42+
case .passwordRecovery:
43+
PasswordRecoveryView()
44+
case .emailLink:
45+
EmailLinkView()
46+
case .authPicker:
47+
if authService.emailSignInEnabled {
48+
Text(authService.authenticationFlow == .login ? authService.string
49+
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
50+
Divider()
51+
EmailAuthView()
52+
}
53+
VStack {
54+
authService.renderButtons()
55+
}.padding(.horizontal)
56+
if authService.emailSignInEnabled {
57+
Divider()
58+
HStack {
59+
Text(authService
60+
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
61+
authService.string.alreadyHaveAnAccountLabel)
62+
Button(action: {
63+
withAnimation {
64+
switchFlow()
65+
}
66+
}) {
67+
Text(authService.authenticationFlow == .signUp ? authService.string
68+
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
69+
.fontWeight(.semibold)
70+
.foregroundColor(.blue)
4971
}
50-
}) {
51-
Text(authService.authenticationFlow == .signUp ? authService.string
52-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
53-
.fontWeight(.semibold)
54-
.foregroundColor(.blue)
5572
}
5673
}
5774
PrivacyTOCsView(displayMode: .footer)
5875
Text(authService.errorMessage).foregroundColor(.red)
76+
default:
77+
// TODO: - possibly refactor this, see: https://github.com/firebase/FirebaseUI-iOS/pull/1259#discussion_r2105473437
78+
EmptyView()
79+
}
80+
}
81+
}.sheet(isPresented: isAuthModalPresented) {
82+
VStack(spacing: 0) {
83+
HStack {
84+
Button(action: {
85+
authService.dismissModal()
86+
}) {
87+
HStack(spacing: 4) {
88+
Image(systemName: "chevron.left")
89+
.font(.system(size: 17, weight: .medium))
90+
Text(authService.string.backButtonLabel)
91+
.font(.system(size: 17))
92+
}
93+
.foregroundColor(.blue)
94+
}
95+
Spacer()
96+
}
97+
.padding()
98+
.background(Color(.systemBackground))
99+
100+
Divider()
101+
102+
if let view = authService.viewForCurrentModal() {
103+
view
104+
.frame(maxWidth: .infinity, maxHeight: .infinity)
105+
.padding()
59106
}
60107
}
61108
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ extension EmailAuthView: View {
123123
}
124124
}
125125
.disabled(!isValid)
126-
.padding([.top, .bottom], 8)
126+
.padding([.top, .bottom, .horizontal], 8)
127127
.frame(maxWidth: .infinity)
128128
.buttonStyle(.borderedProminent)
129129
Button(action: {
Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import FirebaseCore
12
import SwiftUI
23

34
struct PasswordPromptSheet {
@@ -9,26 +10,45 @@ struct PasswordPromptSheet {
910
extension PasswordPromptSheet: View {
1011
var body: some View {
1112
VStack(spacing: 20) {
12-
SecureField(authService.string.passwordInputLabel, text: $password)
13-
.textFieldStyle(.roundedBorder)
13+
Text(authService.string.confirmPasswordInputLabel)
14+
.font(.largeTitle)
15+
.fontWeight(.bold)
1416
.padding()
1517

16-
HStack {
17-
Button(authService.string.cancelButtonLabel) {
18-
coordinator.cancel()
19-
}
20-
Spacer()
21-
Button(authService.string.okButtonLabel) {
22-
coordinator.submit(password: password)
23-
}
24-
.disabled(password.isEmpty)
18+
Divider()
19+
20+
LabeledContent {
21+
TextField(authService.string.passwordInputLabel, text: $password)
22+
.textInputAutocapitalization(.never)
23+
.disableAutocorrection(true)
24+
.submitLabel(.next)
25+
} label: {
26+
Image(systemName: "lock")
27+
}.padding(.vertical, 10)
28+
.background(Divider(), alignment: .bottom)
29+
.padding(.bottom, 4)
30+
31+
Button(action: {
32+
coordinator.submit(password: password)
33+
}) {
34+
Text(authService.string.okButtonLabel)
35+
.padding(.vertical, 8)
36+
.frame(maxWidth: .infinity)
37+
}
38+
.disabled(password.isEmpty)
39+
.padding([.top, .bottom, .horizontal], 8)
40+
.frame(maxWidth: .infinity)
41+
.buttonStyle(.borderedProminent)
42+
43+
Button(authService.string.cancelButtonLabel) {
44+
coordinator.cancel()
2545
}
26-
.padding(.horizontal)
2746
}
2847
.padding()
2948
}
3049
}
3150

3251
#Preview {
33-
PasswordPromptSheet(coordinator: PasswordPromptCoordinator())
52+
FirebaseOptions.dummyConfigurationForPreview()
53+
return PasswordPromptSheet(coordinator: PasswordPromptCoordinator()).environment(AuthService())
3454
}

0 commit comments

Comments
 (0)