Skip to content
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
Expand Up @@ -350,4 +350,11 @@ public class StringUtils {
public var privacyPolicyLabel: String {
return localizedString(for: "PrivacyPolicy")
}

/// Alert Error title
/// found in:
/// PasswordRecoveryView
public var alertErrorTitle: String {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used existing "Error" title for alerts/modal.

return localizedString(for: "Error")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,65 @@ public struct AuthPickerView {
authService.authenticationFlow = authService
.authenticationFlow == .login ? .signUp : .login
}
}

extension AuthPickerView: View {
public var body: some View {
VStack {
@ViewBuilder
private var authPickerTitleView: some View {
if authService.authView == .authPicker {
Text(authService.string.authPickerTitle)
.font(.largeTitle)
.fontWeight(.bold)
.padding()
if authService.authenticationState == .authenticated {
SignedInView()
} else if authService.authView == .passwordRecovery {
PasswordRecoveryView()
} else if authService.authView == .emailLink {
EmailLinkView()
} else {
if authService.emailSignInEnabled {
Text(authService.authenticationFlow == .login ? authService.string
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
Divider()
EmailAuthView()
}
VStack {
authService.renderButtons()
}.padding(.horizontal)
if authService.emailSignInEnabled {
Divider()
HStack {
Text(authService
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
authService.string.alreadyHaveAnAccountLabel)
Button(action: {
withAnimation {
switchFlow()
}
}) {
Text(authService.authenticationFlow == .signUp ? authService.string
}
}
}

extension AuthPickerView: View {
public var body: some View {
ScrollView {
VStack {
authPickerTitleView
if authService.authenticationState == .authenticated {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do this slightly more elegantly with a

switch authService.authView {
  case .passwordRecovery:
    // ...
  case .emailLink:
    // ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using switch statement: c013f34 (#1259)

The only thing I don't like is including the default case at the end to satisfy compiler as we're not using updatePassword View in AuthPickerView.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put break in the switch instead of EmptyView

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-05-28 at 09 13 23

Doesn't seem possible. I can leave a TODO to refactor this.

SignedInView()
} else {
switch authService.authView {
case .passwordRecovery:
PasswordRecoveryView()
case .emailLink:
EmailLinkView()
case .authPicker:
if authService.emailSignInEnabled {
Text(authService.authenticationFlow == .login ? authService.string
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
.fontWeight(.semibold)
.foregroundColor(.blue)
Divider()
EmailAuthView()
}
VStack {
authService.renderButtons()
}.padding(.horizontal)
if authService.emailSignInEnabled {
Divider()
HStack {
Text(authService
.authenticationFlow == .login ? authService.string.dontHaveAnAccountYetLabel :
authService.string.alreadyHaveAnAccountLabel)
Button(action: {
withAnimation {
switchFlow()
}
}) {
Text(authService.authenticationFlow == .signUp ? authService.string
.emailLoginFlowLabel : authService.string.emailSignUpFlowLabel)
.fontWeight(.semibold)
.foregroundColor(.blue)
}
}
}
PrivacyTOCsView(displayMode: .footer)
Text(authService.errorMessage).foregroundColor(.red)
default:
// TODO: - possibly refactor this, see: https://github.com/firebase/FirebaseUI-iOS/pull/1259#discussion_r2105473437
EmptyView()
}
PrivacyTOCsView(displayMode: .footer)
Text(authService.errorMessage).foregroundColor(.red)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import FirebaseCore
import SwiftUI

struct PasswordPromptSheet {
Expand All @@ -9,26 +10,45 @@ struct PasswordPromptSheet {
extension PasswordPromptSheet: View {
var body: some View {
VStack(spacing: 20) {
SecureField(authService.string.passwordInputLabel, text: $password)
.textFieldStyle(.roundedBorder)
Text(authService.string.confirmPasswordInputLabel)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improved the UI for PasswordPromptSheet:

.font(.largeTitle)
.fontWeight(.bold)
.padding()

HStack {
Button(authService.string.cancelButtonLabel) {
coordinator.cancel()
}
Spacer()
Button(authService.string.okButtonLabel) {
coordinator.submit(password: password)
}
.disabled(password.isEmpty)
Divider()

LabeledContent {
TextField(authService.string.passwordInputLabel, text: $password)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.submitLabel(.next)
} label: {
Image(systemName: "lock")
}.padding(.vertical, 10)
.background(Divider(), alignment: .bottom)
.padding(.bottom, 4)

Button(action: {
coordinator.submit(password: password)
}) {
Text(authService.string.okButtonLabel)
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
.disabled(password.isEmpty)
.padding([.top, .bottom, .horizontal], 8)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)

Button(authService.string.cancelButtonLabel) {
coordinator.cancel()
}
.padding(.horizontal)
}
.padding()
}
}

#Preview {
PasswordPromptSheet(coordinator: PasswordPromptCoordinator())
FirebaseOptions.dummyConfigurationForPreview()
return PasswordPromptSheet(coordinator: PasswordPromptCoordinator()).environment(AuthService())
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import FirebaseCore
import SwiftUI

private struct ResultWrapper: Identifiable {
let id = UUID()
let value: Result<Void, Error>
}

public struct PasswordRecoveryView {
@Environment(AuthService.self) private var authService
@State private var email = ""
@State private var showModal = false
@State private var resultWrapper: ResultWrapper?

public init() {}

private func sendPasswordRecoveryEmail() async {
let recoveryResult: Result<Void, Error>

do {
try await authService.sendPasswordRecoveryEmail(to: email)
showModal = true
} catch {}
resultWrapper = ResultWrapper(value: .success(()))
} catch {
resultWrapper = ResultWrapper(value: .failure(error))
}
}
}

Expand All @@ -33,9 +42,11 @@ extension PasswordRecoveryView: View {
.submitLabel(.next)
} label: {
Image(systemName: "at")
}.padding(.vertical, 6)
.background(Divider(), alignment: .bottom)
.padding(.bottom, 4)
}
.padding(.vertical, 6)
.background(Divider(), alignment: .bottom)
.padding(.bottom, 4)

Button(action: {
Task {
await sendPasswordRecoveryEmail()
Expand All @@ -46,11 +57,29 @@ extension PasswordRecoveryView: View {
.frame(maxWidth: .infinity)
}
.disabled(!CommonUtils.isValidEmail(email))
.padding([.top, .bottom], 8)
.padding([.top, .bottom, .horizontal], 8)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
}.sheet(isPresented: $showModal) {
VStack {
}
.sheet(item: $resultWrapper) { wrapper in
resultSheet(wrapper.value)
}
.navigationBarItems(leading: Button(action: {
authService.authView = .authPicker
}) {
Image(systemName: "chevron.left")
.foregroundColor(.blue)
Text(authService.string.backButtonLabel)
.foregroundColor(.blue)
})
}

@ViewBuilder
@MainActor
private func resultSheet(_ result: Result<Void, Error>) -> some View {
VStack {
switch result {
case .success:
Text(authService.string.passwordRecoveryEmailSentTitle)
.font(.largeTitle)
.fontWeight(.bold)
Expand All @@ -60,25 +89,29 @@ extension PasswordRecoveryView: View {

Divider()

Text(authService.string.passwordRecoveryEmailSentMessage)
Text(String(format: authService.string.passwordRecoveryEmailSentMessage, email))
.padding()

case .failure:
Text(authService.string.alertErrorTitle)
.font(.title)
.fontWeight(.semibold)
.padding()

Divider()

Text(authService.errorMessage)
.padding()
Button(authService.string.okButtonLabel) {
showModal = false
}
.padding()
}

Divider()

Button(authService.string.okButtonLabel) {
self.resultWrapper = nil
}
.padding()
}.onOpenURL { _ in
// move the user to email/password View
}
.navigationBarItems(leading: Button(action: {
authService.authView = .authPicker
}) {
Image(systemName: "chevron.left")
.foregroundColor(.blue)
Text(authService.string.backButtonLabel)
.foregroundColor(.blue)
})
.padding()
}
}

Expand Down