Skip to content

Commit 47b3a60

Browse files
committed
refactor: use provider styles
1 parent d698c29 commit 47b3a60

File tree

77 files changed

+1536
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1536
-230
lines changed

FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Views/SignInWithAppleButton.swift

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
import FirebaseAuthSwiftUI
16+
import FirebaseAuthUIComponents
1617
import SwiftUI
1718

1819
/// A button for signing in with Apple
@@ -27,27 +28,14 @@ public struct SignInWithAppleButton {
2728

2829
extension SignInWithAppleButton: View {
2930
public var body: some View {
30-
Button(action: {
31+
AuthProviderButton(
32+
label: "Sign in with Apple",
33+
style: .apple,
34+
accessibilityId: "sign-in-with-apple-button"
35+
) {
3136
Task {
3237
try? await authService.signIn(provider)
3338
}
34-
}) {
35-
HStack {
36-
Image(systemName: "apple.logo")
37-
.resizable()
38-
.renderingMode(.template)
39-
.scaledToFit()
40-
.frame(width: 24, height: 24)
41-
.foregroundColor(.white)
42-
Text("Sign in with Apple")
43-
.fontWeight(.semibold)
44-
.foregroundColor(.white)
45-
}
46-
.frame(maxWidth: .infinity, alignment: .leading)
47-
.padding()
48-
.background(Color.black)
49-
.cornerRadius(8)
5039
}
51-
.accessibilityIdentifier("sign-in-with-apple-button")
5240
}
5341
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Strings/Localizable.xcstrings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@
204204
"en" : {
205205
"stringUnit" : {
206206
"state" : "translated",
207-
"value" : "Welcome"
207+
"value" : "Sign in with Firebase"
208208
}
209209
}
210210
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ public struct AuthPickerView {
2222
public init() {}
2323

2424
private func switchFlow() {
25-
authService.authenticationFlow = authService
26-
.authenticationFlow == .signIn ? .signUp : .signIn
25+
authService.authenticationFlow =
26+
authService
27+
.authenticationFlow == .signIn ? .signUp : .signIn
2728
}
2829

2930
@ViewBuilder
@@ -41,7 +42,7 @@ extension AuthPickerView: View {
4142
public var body: some View {
4243
ScrollView {
4344
VStack {
44-
authPickerTitleView
45+
//authPickerTitleView
4546
if authService.authenticationState == .authenticated {
4647
switch authService.authView {
4748
case .mfaEnrollment:
@@ -63,29 +64,39 @@ extension AuthPickerView: View {
6364
MFAResolutionView()
6465
case .authPicker:
6566
if authService.emailSignInEnabled {
66-
Text(authService.authenticationFlow == .signIn ? authService.string
67-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
68-
Divider()
67+
// Text(
68+
// authService.authenticationFlow == .signIn
69+
// ? authService.string
70+
// .emailLoginFlowLabel
71+
// : authService.string.emailSignUpFlowLabel
72+
// )
6973
EmailAuthView()
7074
}
7175
VStack {
7276
authService.renderButtons()
73-
}.padding(.horizontal)
77+
}
7478
if authService.emailSignInEnabled {
7579
Divider()
7680
HStack {
77-
Text(authService
78-
.authenticationFlow == .signIn ? authService.string.dontHaveAnAccountYetLabel :
79-
authService.string.alreadyHaveAnAccountLabel)
81+
Text(
82+
authService
83+
.authenticationFlow == .signIn
84+
? authService.string.dontHaveAnAccountYetLabel
85+
: authService.string.alreadyHaveAnAccountLabel
86+
)
8087
Button(action: {
8188
withAnimation {
8289
switchFlow()
8390
}
8491
}) {
85-
Text(authService.authenticationFlow == .signUp ? authService.string
86-
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
87-
.fontWeight(.semibold)
88-
.foregroundColor(.blue)
92+
Text(
93+
authService.authenticationFlow == .signUp
94+
? authService.string
95+
.emailLoginFlowLabel
96+
: authService.string.emailSignUpFlowLabel
97+
)
98+
.fontWeight(.semibold)
99+
.foregroundColor(.blue)
89100
}.accessibilityIdentifier("switch-auth-flow")
90101
}
91102
}
@@ -96,11 +107,16 @@ extension AuthPickerView: View {
96107
}
97108
}
98109
}
110+
.safeAreaPadding()
111+
.navigationTitle(authService.string.authPickerTitle)
99112
}
100-
.errorAlert(error: Binding(
101-
get: { authService.currentError },
102-
set: { authService.currentError = $0 }
103-
), okButtonLabel: authService.string.okButtonLabel)
113+
.errorAlert(
114+
error: Binding(
115+
get: { authService.currentError },
116+
set: { authService.currentError = $0 }
117+
),
118+
okButtonLabel: authService.string.okButtonLabel
119+
)
104120
}
105121
}
106122

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift

Lines changed: 80 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -31,107 +31,120 @@ private enum FocusableField: Hashable {
3131
@MainActor
3232
public struct EmailAuthView {
3333
@Environment(AuthService.self) private var authService
34-
34+
3535
@State private var email = ""
3636
@State private var password = ""
3737
@State private var confirmPassword = ""
38-
38+
3939
@FocusState private var focus: FocusableField?
40-
40+
4141
public init() {}
42-
42+
4343
private var isValid: Bool {
4444
return if authService.authenticationFlow == .signIn {
4545
!email.isEmpty && !password.isEmpty
4646
} else {
4747
!email.isEmpty && !password.isEmpty && password == confirmPassword
4848
}
4949
}
50-
50+
5151
private func signInWithEmailPassword() async {
5252
try? await authService.signIn(email: email, password: password)
5353
}
54-
54+
5555
private func createUserWithEmailPassword() async {
5656
try? await authService.createUser(email: email, password: password)
5757
}
5858
}
5959

6060
extension EmailAuthView: View {
6161
public var body: some View {
62-
VStack {
63-
LabeledContent {
64-
TextField(authService.string.emailInputLabel, text: $email)
65-
.textInputAutocapitalization(.never)
66-
.disableAutocorrection(true)
67-
.focused($focus, equals: .email)
68-
.submitLabel(.next)
69-
.onSubmit {
70-
self.focus = .password
71-
}
72-
} label: {
73-
Image(systemName: "at")
74-
}
75-
.padding(.vertical, 6)
76-
.background(Divider(), alignment: .bottom)
77-
.padding(.bottom, 4)
62+
VStack(spacing: 16) {
63+
AuthTextField(
64+
text: $email,
65+
localizedTitle: "Email",
66+
prompt: authService.string.emailInputLabel,
67+
keyboardType: .emailAddress,
68+
contentType: .emailAddress,
69+
onSubmit: { _ in
70+
self.focus = .password
71+
},
72+
leading: {
73+
Image(systemName: "at")
74+
}
75+
)
76+
.focused($focus, equals: .email)
7877
.accessibilityIdentifier("email-field")
79-
80-
LabeledContent {
81-
SecureField(authService.string.passwordInputLabel, text: $password)
82-
.focused($focus, equals: .password)
83-
.textInputAutocapitalization(.never)
84-
.disableAutocorrection(true)
85-
.submitLabel(.go)
86-
.onSubmit {
87-
Task { await signInWithEmailPassword() }
88-
}
89-
} label: {
90-
Image(systemName: "lock")
91-
}
92-
.padding(.vertical, 6)
93-
.background(Divider(), alignment: .bottom)
94-
.padding(.bottom, 8)
78+
AuthTextField(
79+
text: $password,
80+
localizedTitle: "Password",
81+
prompt: authService.string.passwordInputLabel,
82+
contentType: .password,
83+
sensitive: true,
84+
onSubmit: { _ in
85+
Task { await signInWithEmailPassword() }
86+
},
87+
leading: {
88+
Image(systemName: "lock")
89+
}
90+
)
91+
.submitLabel(.go)
92+
.focused($focus, equals: .password)
9593
.accessibilityIdentifier("password-field")
96-
9794
if authService.authenticationFlow == .signIn {
98-
Button(action: {
99-
authService.authView = .passwordRecovery
100-
}) {
95+
NavigationLink {
96+
PasswordRecoveryView()
97+
.environment(authService)
98+
} label: {
10199
Text(authService.string.passwordButtonLabel)
102-
}.accessibilityIdentifier("password-recovery-button")
103-
}
100+
.frame(maxWidth: .infinity, alignment: .trailing)
101+
}
102+
.accessibilityIdentifier("password-recovery-button")
104103

104+
// Button(action: {
105+
// authService.authView = .passwordRecovery
106+
// }) {
107+
// Text(authService.string.passwordButtonLabel)
108+
// }.accessibilityIdentifier("password-recovery-button")
109+
}
110+
105111
if authService.authenticationFlow == .signUp {
106-
LabeledContent {
107-
SecureField(authService.string.confirmPasswordInputLabel, text: $confirmPassword)
108-
.focused($focus, equals: .confirmPassword)
109-
.textInputAutocapitalization(.never)
110-
.disableAutocorrection(true)
111-
.submitLabel(.go)
112-
.onSubmit {
113-
Task { await createUserWithEmailPassword() }
114-
}
115-
} label: {
116-
Image(systemName: "lock")
117-
}
118-
.padding(.vertical, 6)
119-
.background(Divider(), alignment: .bottom)
120-
.padding(.bottom, 8)
112+
AuthTextField(
113+
text: $confirmPassword,
114+
localizedTitle: "Confirm Password",
115+
prompt: authService.string.confirmPasswordInputLabel,
116+
contentType: .password,
117+
sensitive: true,
118+
onSubmit: { _ in
119+
Task { await createUserWithEmailPassword() }
120+
},
121+
leading: {
122+
Image(systemName: "lock")
123+
}
124+
)
125+
.submitLabel(.go)
126+
.focused($focus, equals: .confirmPassword)
121127
.accessibilityIdentifier("confirm-password-field")
122128
}
123-
129+
124130
Button(action: {
125131
Task {
126-
if authService.authenticationFlow == .signIn { await signInWithEmailPassword() }
127-
else { await createUserWithEmailPassword() }
132+
if authService.authenticationFlow == .signIn {
133+
await signInWithEmailPassword()
134+
} else {
135+
await createUserWithEmailPassword()
136+
}
128137
}
129138
}) {
130139
if authService.authenticationState != .authenticating {
131-
Text(authService.authenticationFlow == .signIn ? authService.string
132-
.signInWithEmailButtonLabel : authService.string.signUpWithEmailButtonLabel)
133-
.padding(.vertical, 8)
134-
.frame(maxWidth: .infinity)
140+
Text(
141+
authService.authenticationFlow == .signIn
142+
? authService.string
143+
.signInWithEmailButtonLabel
144+
: authService.string.signUpWithEmailButtonLabel
145+
)
146+
.padding(.vertical, 8)
147+
.frame(maxWidth: .infinity)
135148
} else {
136149
ProgressView()
137150
.progressViewStyle(CircularProgressViewStyle(tint: .white))
@@ -140,7 +153,7 @@ extension EmailAuthView: View {
140153
}
141154
}
142155
.disabled(!isValid)
143-
.padding([.top, .bottom, .horizontal], 8)
156+
.padding([.top, .bottom], 8)
144157
.frame(maxWidth: .infinity)
145158
.buttonStyle(.borderedProminent)
146159
.accessibilityIdentifier("sign-in-button")
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
15+
import SwiftUI
16+
17+
public struct AnnotatedString: View {
18+
let fullText: String
19+
let links: [(label: String, url: String)]
20+
21+
public init(
22+
fullText: String,
23+
links: [(String, String)]
24+
) {
25+
self.fullText = fullText
26+
self.links = links
27+
}
28+
29+
public var body: some View {
30+
let text = makeAttributedText()
31+
Text(text)
32+
.multilineTextAlignment(.center)
33+
.tint(.accentColor)
34+
.onOpenURL { url in
35+
UIApplication.shared.open(url)
36+
}
37+
}
38+
39+
private func makeAttributedText() -> AttributedString {
40+
let template = fullText
41+
var attributed = AttributedString(template)
42+
43+
for (label, urlString) in links {
44+
guard let range = attributed.range(of: label),
45+
let url = URL(string: urlString)
46+
else { continue }
47+
48+
attributed[range].link = url
49+
attributed[range].foregroundColor = UIColor.tintColor
50+
attributed[range].underlineStyle = Text.LineStyle.single
51+
}
52+
53+
return attributed
54+
}
55+
}

0 commit comments

Comments
 (0)