diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift index 5c3287f566..ee7340d76e 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthConfiguration.swift @@ -6,8 +6,8 @@ public struct AuthConfiguration { let interactiveDismissEnabled: Bool let shouldAutoUpgradeAnonymousUsers: Bool let customStringsBundle: Bundle? - let tosUrl: URL - let privacyPolicyUrl: URL + let tosUrl: URL? + let privacyPolicyUrl: URL? let emailLinkSignInActionCodeSettings: ActionCodeSettings? let verifyEmailActionCodeSettings: ActionCodeSettings? @@ -15,8 +15,8 @@ public struct AuthConfiguration { interactiveDismissEnabled: Bool = true, shouldAutoUpgradeAnonymousUsers: Bool = false, customStringsBundle: Bundle? = nil, - tosUrl: URL = URL(string: "https://example.com/tos")!, - privacyPolicyUrl: URL = URL(string: "https://example.com/privacy")!, + tosUrl: URL? = nil, + privacyPolicyUrl: URL? = nil, emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, verifyEmailActionCodeSettings: ActionCodeSettings? = nil) { self.shouldHideCancelButton = shouldHideCancelButton diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift index 170632b512..1dbdc49a48 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Utils/StringUtils.swift @@ -308,12 +308,14 @@ public class StringUtils { public var enterPhoneNumberLabel: String { return localizedString(for: "Enter phone number") } + /// Phone provider /// found in: /// - PhoneAuthButtonView public var phoneNumberVerificationCodeLabel: String { return localizedString(for: "Enter verification code") } + /// Phone provider /// found in: /// - PhoneAuthButtonView @@ -327,4 +329,25 @@ public class StringUtils { public var verifyPhoneNumberAndSignInLabel: String { return localizedString(for: "Verify phone number and sign-in") } + + /// Terms of Service label + /// found in: + /// - PrivacyTOCsView + public var termsOfServiceLabel: String { + return localizedString(for: "TermsOfService") + } + + /// Terms of Service message + /// found in: + /// - PrivacyTOCsView + public var termsOfServiceMessage: String { + return localizedString(for: "TermsOfServiceMessage") + } + + /// Privacy Policy + /// found in: + /// - PrivacyTOCsView + public var privacyPolicyLabel: String { + return localizedString(for: "PrivacyPolicy") + } } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift index 232e853aef..696b541c3d 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift @@ -50,6 +50,7 @@ extension AuthPickerView: View { .foregroundColor(.blue) } } + PrivacyTOCsView(displayMode: .footer) Text(authService.errorMessage).foregroundColor(.red) } } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift new file mode 100644 index 0000000000..bd82ed27f9 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift @@ -0,0 +1,75 @@ +// +// PrivacyTOCsView.swift +// FirebaseUI +// +// Created by Russell Wheatley on 12/05/2025. +// +import FirebaseCore +import SwiftUI + +@MainActor +struct PrivacyTOCsView { + @Environment(AuthService.self) private var authService + enum DisplayMode { + case full, footer + } + + let displayMode: DisplayMode + + public init(displayMode: DisplayMode = .full) { + self.displayMode = displayMode + } + + private func attributedMessage(tosURL: URL, privacyURL: URL) -> AttributedString { + let tosText = authService.string.termsOfServiceLabel + let privacyText = authService.string.privacyPolicyLabel + + let format: String = displayMode == .full + ? authService.string.termsOfServiceMessage + : "%@ %@" + + let fullText = String(format: format, tosText, privacyText) + + var attributed = AttributedString(fullText) + + if let tosRange = attributed.range(of: tosText) { + attributed[tosRange].link = tosURL + attributed[tosRange].foregroundColor = .blue + } + + if let privacyRange = attributed.range(of: privacyText) { + attributed[privacyRange].link = privacyURL + attributed[privacyRange].foregroundColor = .blue + } + + return attributed + } +} + +extension PrivacyTOCsView: View { + public var body: some View { + Group { + if let tosURL = authService.configuration.tosUrl, + let privacyURL = authService.configuration.privacyPolicyUrl { + Text(attributedMessage(tosURL: tosURL, privacyURL: privacyURL)) + .multilineTextAlignment(displayMode == .full ? .leading : .trailing) + .font(.footnote) + .foregroundColor(.primary) + .padding() + } else { + EmptyView() + } + } + } +} + +#Preview { + FirebaseOptions.dummyConfigurationForPreview() + let configuration = AuthConfiguration( + tosUrl: URL(string: "https://example.com/tos"), + privacyPolicyUrl: URL(string: "https://example.com/privacy") + ) + let authService = AuthService(configuration: configuration) + return PrivacyTOCsView(displayMode: .footer) + .environment(authService) +} diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 4d5c05bd21..a861265ed4 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -75,9 +75,9 @@ public struct AuthConfiguration { // custom string bundle for string localizations let customStringsBundle: Bundle? // terms of service URL - let tosUrl: URL + let tosUrl: URL? // privacy policy URL - let privacyPolicyUrl: URL + let privacyPolicyUrl: URL? // action code settings for email sign in link let emailLinkSignInActionCodeSettings: ActionCodeSettings? // action code settings verifying email address @@ -87,13 +87,15 @@ public struct AuthConfiguration { interactiveDismissEnabled: Bool = true, shouldAutoUpgradeAnonymousUsers: Bool = false, customStringsBundle: Bundle? = nil, - tosUrl: URL = URL(string: "https://example.com/tos")!, - privacyPolicyUrl: URL = URL(string: "https://example.com/privacy")!, + tosUrl: URL? = nil, + privacyPolicyUrl: URL? = nil, emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, verifyEmailActionCodeSettings: ActionCodeSettings? = nil) } ``` +> Note: Both `tosUrl` and `privacyPolicyUrl` have to be set for them to be rendered in the UI. + ## Configuring providers 1. Ensure the provider is installed from step 1 (e.g. if configuring Google provider, you need to install `FirebaseGoogleSwiftUI` package). diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift index 9547de0308..b85492a77b 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift @@ -26,6 +26,8 @@ struct ContentView: View { actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!) let configuration = AuthConfiguration( shouldAutoUpgradeAnonymousUsers: true, + tosUrl: URL(string: "https://example.com/tos"), + privacyPolicyUrl: URL(string: "https://example.com/privacy"), emailLinkSignInActionCodeSettings: actionCodeSettings ) authService = AuthService(