diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt index aca1ccf9e..ef70c5f3b 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt @@ -14,29 +14,29 @@ package com.firebase.ui.auth.compose.configuration +import android.content.Context import java.util.Locale import com.google.firebase.auth.ActionCodeSettings import androidx.compose.ui.graphics.vector.ImageVector +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider -fun actionCodeSettings( - block: ActionCodeSettings.Builder.() -> Unit -) = ActionCodeSettings.newBuilder().apply(block).build() +fun actionCodeSettings(block: ActionCodeSettings.Builder.() -> Unit) = + ActionCodeSettings.newBuilder().apply(block).build() -fun authUIConfiguration(block: AuthUIConfigurationBuilder.() -> Unit): AuthUIConfiguration { - val builder = AuthUIConfigurationBuilder() - builder.block() - return builder.build() -} +fun authUIConfiguration(block: AuthUIConfigurationBuilder.() -> Unit) = + AuthUIConfigurationBuilder().apply(block).build() @DslMarker annotation class AuthUIConfigurationDsl @AuthUIConfigurationDsl class AuthUIConfigurationBuilder { + var context: Context? = null private val providers = mutableListOf() var theme: AuthUITheme = AuthUITheme.Default - var stringProvider: AuthUIStringProvider? = null var locale: Locale? = null + var stringProvider: AuthUIStringProvider? = null var isCredentialManagerEnabled: Boolean = true var isMfaEnabled: Boolean = true var isAnonymousUpgradeEnabled: Boolean = false @@ -48,36 +48,16 @@ class AuthUIConfigurationBuilder { var isDisplayNameRequired: Boolean = true var isProviderChoiceAlwaysShown: Boolean = false - fun providers(block: AuthProvidersBuilder.() -> Unit) { - val builder = AuthProvidersBuilder() - builder.block() - providers.addAll(builder.build()) - } + fun providers(block: AuthProvidersBuilder.() -> Unit) = + providers.addAll(AuthProvidersBuilder().apply(block).build()) internal fun build(): AuthUIConfiguration { - validate() - return AuthUIConfiguration( - providers = providers.toList(), - theme = theme, - stringProvider = stringProvider, - locale = locale, - isCredentialManagerEnabled = isCredentialManagerEnabled, - isMfaEnabled = isMfaEnabled, - isAnonymousUpgradeEnabled = isAnonymousUpgradeEnabled, - tosUrl = tosUrl, - privacyPolicyUrl = privacyPolicyUrl, - logo = logo, - actionCodeSettings = actionCodeSettings, - isNewEmailAccountsAllowed = isNewEmailAccountsAllowed, - isDisplayNameRequired = isDisplayNameRequired, - isProviderChoiceAlwaysShown = isProviderChoiceAlwaysShown - ) - } + val context = requireNotNull(context) { + "Application context is required" + } - private fun validate() { - // At least one provider - if (providers.isEmpty()) { - throw IllegalArgumentException("At least one provider must be configured") + require(providers.isNotEmpty()) { + "At least one provider must be configured" } // No unsupported providers @@ -113,6 +93,24 @@ class AuthUIConfigurationBuilder { else -> null } } + + return AuthUIConfiguration( + context = context, + providers = providers.toList(), + theme = theme, + locale = locale, + stringProvider = stringProvider ?: DefaultAuthUIStringProvider(context, locale), + isCredentialManagerEnabled = isCredentialManagerEnabled, + isMfaEnabled = isMfaEnabled, + isAnonymousUpgradeEnabled = isAnonymousUpgradeEnabled, + tosUrl = tosUrl, + privacyPolicyUrl = privacyPolicyUrl, + logo = logo, + actionCodeSettings = actionCodeSettings, + isNewEmailAccountsAllowed = isNewEmailAccountsAllowed, + isDisplayNameRequired = isDisplayNameRequired, + isProviderChoiceAlwaysShown = isProviderChoiceAlwaysShown + ) } } @@ -120,6 +118,11 @@ class AuthUIConfigurationBuilder { * Configuration object for the authentication flow. */ class AuthUIConfiguration( + /** + * Application context + */ + val context: Context, + /** * The list of enabled authentication providers. */ @@ -131,14 +134,14 @@ class AuthUIConfiguration( val theme: AuthUITheme = AuthUITheme.Default, /** - * A custom provider for localized strings. + * The locale for internationalization. */ - val stringProvider: AuthUIStringProvider? = null, + val locale: Locale? = null, /** - * The locale for internationalization. + * A custom provider for localized strings. */ - val locale: Locale? = null, + val stringProvider: AuthUIStringProvider = DefaultAuthUIStringProvider(context, locale), /** * Enables integration with Android's Credential Manager API. Defaults to true. diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt deleted file mode 100644 index 0e7080722..000000000 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2025 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.firebase.ui.auth.compose.configuration - -import android.content.Context -import com.firebase.ui.auth.R - -/** - * An interface for providing localized string resources. This interface defines methods for all - * user-facing strings, such as initializing(), signInWithGoogle(), invalidEmail(), - * passwordsDoNotMatch(), etc., allowing for complete localization of the UI. - */ -interface AuthUIStringProvider { - /** Loading text displayed during initialization or processing states */ - val initializing: String - - /** Button text for Google sign-in option */ - val signInWithGoogle: String - - /** Error message when email address field is empty */ - val missingEmailAddress: String - - /** Error message when email address format is invalid */ - val invalidEmailAddress: String - - /** Generic error message for incorrect password during sign-in */ - val invalidPassword: String - - /** Error message when password confirmation doesn't match the original password */ - val passwordsDoNotMatch: String - - /** Error message when password doesn't meet minimum length requirement. Should support string formatting with minimum length parameter. */ - val passwordTooShort: String - - /** Error message when password is missing at least one uppercase letter (A-Z) */ - val passwordMissingUppercase: String - - /** Error message when password is missing at least one lowercase letter (a-z) */ - val passwordMissingLowercase: String - - /** Error message when password is missing at least one numeric digit (0-9) */ - val passwordMissingDigit: String - - /** Error message when password is missing at least one special character */ - val passwordMissingSpecialCharacter: String -} - -internal class DefaultAuthUIStringProvider(private val context: Context) : AuthUIStringProvider { - override val initializing: String get() = "" - override val signInWithGoogle: String - get() = context.getString(R.string.fui_sign_in_with_google) - override val missingEmailAddress: String - get() = context.getString(R.string.fui_missing_email_address) - override val invalidEmailAddress: String - get() = context.getString(R.string.fui_invalid_email_address) - override val invalidPassword: String - get() = context.getString(R.string.fui_error_invalid_password) - override val passwordsDoNotMatch: String get() = "" - override val passwordTooShort: String - get() = context.getString(R.string.fui_error_password_too_short) - override val passwordMissingUppercase: String - get() = context.getString(R.string.fui_error_password_missing_uppercase) - override val passwordMissingLowercase: String - get() = context.getString(R.string.fui_error_password_missing_lowercase) - override val passwordMissingDigit: String - get() = context.getString(R.string.fui_error_password_missing_digit) - override val passwordMissingSpecialCharacter: String - get() = context.getString(R.string.fui_error_password_missing_special_character) -} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt index 8f53822f2..5c5d5b125 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt @@ -14,6 +14,8 @@ package com.firebase.ui.auth.compose.configuration +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider + /** * An abstract class representing a set of validation rules that can be applied to a password field, * typically within the [AuthProvider.Email] configuration. diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt new file mode 100644 index 000000000..646727fb7 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.stringprovider + +/** + * An interface for providing localized string resources. This interface defines methods for all + * user-facing strings, such as initializing(), signInWithGoogle(), invalidEmailAddress(), + * passwordsDoNotMatch(), etc., allowing for complete localization of the UI. + * + * @sample AuthUIStringProviderSample + */ +interface AuthUIStringProvider { + /** Loading text displayed during initialization or processing states */ + val initializing: String + + /** Text for Google Provider */ + val googleProvider: String + + /** Text for Facebook Provider */ + val facebookProvider: String + + /** Text for Twitter Provider */ + val twitterProvider: String + + /** Text for Github Provider */ + val githubProvider: String + + /** Text for Phone Provider */ + val phoneProvider: String + + /** Text for Email Provider */ + val emailProvider: String + + /** Button text for Google sign-in option */ + val signInWithGoogle: String + + /** Button text for Facebook sign-in option */ + val signInWithFacebook: String + + /** Button text for Twitter sign-in option */ + val signInWithTwitter: String + + /** Button text for Github sign-in option */ + val signInWithGithub: String + + /** Button text for Email sign-in option */ + val signInWithEmail: String + + /** Button text for Phone sign-in option */ + val signInWithPhone: String + + /** Button text for Anonymous sign-in option */ + val signInAnonymously: String + + /** Button text for Apple sign-in option */ + val signInWithApple: String + + /** Button text for Microsoft sign-in option */ + val signInWithMicrosoft: String + + /** Button text for Yahoo sign-in option */ + val signInWithYahoo: String + + /** Error message when email address field is empty */ + val missingEmailAddress: String + + /** Error message when email address format is invalid */ + val invalidEmailAddress: String + + /** Generic error message for incorrect password during sign-in */ + val invalidPassword: String + + /** Error message when password confirmation doesn't match the original password */ + val passwordsDoNotMatch: String + + /** Error message when password doesn't meet minimum length requirement. Should support string formatting with minimum length parameter. */ + val passwordTooShort: String + + /** Error message when password is missing at least one uppercase letter (A-Z) */ + val passwordMissingUppercase: String + + /** Error message when password is missing at least one lowercase letter (a-z) */ + val passwordMissingLowercase: String + + /** Error message when password is missing at least one numeric digit (0-9) */ + val passwordMissingDigit: String + + /** Error message when password is missing at least one special character */ + val passwordMissingSpecialCharacter: String + + // Email Authentication Strings + /** Title for email signup form */ + val titleRegisterEmail: String + + /** Hint for email input field */ + val emailHint: String + + /** Hint for password input field */ + val passwordHint: String + + /** Hint for new password input field */ + val newPasswordHint: String + + /** Hint for name input field */ + val nameHint: String + + /** Button text to save form */ + val buttonTextSave: String + + /** Welcome back header for email users */ + val welcomeBackEmailHeader: String + + /** Trouble signing in link text */ + val troubleSigningIn: String + + // Phone Authentication Strings + /** Phone number entry form title */ + val verifyPhoneNumberTitle: String + + /** Hint for phone input field */ + val phoneHint: String + + /** Hint for country input field */ + val countryHint: String + + /** Invalid phone number error */ + val invalidPhoneNumber: String + + /** Phone verification code entry form title */ + val enterConfirmationCode: String + + /** Button text to verify phone number */ + val verifyPhoneNumber: String + + /** Resend code countdown timer */ + val resendCodeIn: String + + /** Resend code link text */ + val resendCode: String + + /** Verifying progress text */ + val verifying: String + + /** Wrong verification code error */ + val incorrectCodeDialogBody: String + + /** SMS terms of service warning */ + val smsTermsOfService: String + + // Provider Picker Strings + /** Common button text for sign in */ + val signInDefault: String + + /** Common button text for continue */ + val continueText: String + + /** Common button text for next */ + val nextDefault: String + + // General Error Messages + /** General unknown error message */ + val errorUnknown: String + + /** Required field error */ + val requiredField: String + + /** Loading progress text */ + val progressDialogLoading: String + + /** Network error message */ + val noInternet: String + + /** TOTP Code prompt */ + val enterTOTPCode: String +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt new file mode 100644 index 000000000..7ddf64522 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.stringprovider + +import android.content.Context +import com.firebase.ui.auth.compose.configuration.AuthProvider +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.authUIConfiguration + +class AuthUIStringProviderSample { + /** + * Override specific strings while delegating others to default provider + */ + class CustomAuthUIStringProvider( + private val defaultProvider: AuthUIStringProvider + ) : AuthUIStringProvider by defaultProvider { + + // Override only the strings you want to customize + override val signInWithGoogle: String = "Continue with Google • MyApp" + override val signInWithFacebook: String = "Continue with Facebook • MyApp" + + // Add custom branding to common actions + override val continueText: String = "Continue to MyApp" + override val signInDefault: String = "Sign in to MyApp" + + // Custom MFA messaging + override val enterTOTPCode: String = + "Enter the 6-digit code from your authenticator app to secure your MyApp account" + } + + fun createCustomConfiguration(applicationContext: Context): AuthUIConfiguration { + val customStringProvider = + CustomAuthUIStringProvider(DefaultAuthUIStringProvider(applicationContext)) + return authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = listOf(), + serverClientId = "" + ) + ) + } + stringProvider = customStringProvider + } + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt new file mode 100644 index 000000000..96a74cdd7 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.stringprovider + +import android.content.Context +import android.content.res.Configuration +import com.firebase.ui.auth.R +import java.util.Locale + +class DefaultAuthUIStringProvider( + private val context: Context, + private val locale: Locale? = null, +) : AuthUIStringProvider { + /** + * Allows overriding locale. + */ + private val localizedContext = locale?.let { locale -> + context.createConfigurationContext( + Configuration(context.resources.configuration).apply { + setLocale(locale) + } + ) + } ?: context + + /** + * Common Strings + */ + override val initializing: String + get() = "Initializing" + + /** + * Auth Provider strings + */ + override val googleProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_google) + override val facebookProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_facebook) + override val twitterProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_twitter) + override val githubProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_github) + override val phoneProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_phone) + override val emailProvider: String + get() = localizedContext.getString(R.string.fui_idp_name_email) + + /** + * Auth Provider Button Strings + */ + override val signInWithGoogle: String + get() = localizedContext.getString(R.string.fui_sign_in_with_google) + override val signInWithFacebook: String + get() = localizedContext.getString(R.string.fui_sign_in_with_facebook) + override val signInWithTwitter: String + get() = localizedContext.getString(R.string.fui_sign_in_with_twitter) + override val signInWithGithub: String + get() = localizedContext.getString(R.string.fui_sign_in_with_github) + override val signInWithEmail: String + get() = localizedContext.getString(R.string.fui_sign_in_with_email) + override val signInWithPhone: String + get() = localizedContext.getString(R.string.fui_sign_in_with_phone) + override val signInAnonymously: String + get() = localizedContext.getString(R.string.fui_sign_in_anonymously) + override val signInWithApple: String + get() = localizedContext.getString(R.string.fui_sign_in_with_apple) + override val signInWithMicrosoft: String + get() = localizedContext.getString(R.string.fui_sign_in_with_microsoft) + override val signInWithYahoo: String + get() = localizedContext.getString(R.string.fui_sign_in_with_yahoo) + + /** + * Email Validator Strings + */ + override val missingEmailAddress: String + get() = localizedContext.getString(R.string.fui_missing_email_address) + override val invalidEmailAddress: String + get() = localizedContext.getString(R.string.fui_invalid_email_address) + + /** + * Password Validator Strings + */ + override val invalidPassword: String + get() = localizedContext.getString(R.string.fui_error_invalid_password) + override val passwordsDoNotMatch: String + get() = localizedContext.getString(R.string.fui_passwords_do_not_match) + override val passwordTooShort: String + get() = localizedContext.getString(R.string.fui_error_password_too_short) + override val passwordMissingUppercase: String + get() = localizedContext.getString(R.string.fui_error_password_missing_uppercase) + override val passwordMissingLowercase: String + get() = localizedContext.getString(R.string.fui_error_password_missing_lowercase) + override val passwordMissingDigit: String + get() = localizedContext.getString(R.string.fui_error_password_missing_digit) + override val passwordMissingSpecialCharacter: String + get() = localizedContext.getString(R.string.fui_error_password_missing_special_character) + + /** + * Email Authentication Strings + */ + override val titleRegisterEmail: String + get() = localizedContext.getString(R.string.fui_title_register_email) + override val emailHint: String + get() = localizedContext.getString(R.string.fui_email_hint) + override val passwordHint: String + get() = localizedContext.getString(R.string.fui_password_hint) + override val newPasswordHint: String + get() = localizedContext.getString(R.string.fui_new_password_hint) + override val nameHint: String + get() = localizedContext.getString(R.string.fui_name_hint) + override val buttonTextSave: String + get() = localizedContext.getString(R.string.fui_button_text_save) + override val welcomeBackEmailHeader: String + get() = localizedContext.getString(R.string.fui_welcome_back_email_header) + override val troubleSigningIn: String + get() = localizedContext.getString(R.string.fui_trouble_signing_in) + + /** + * Phone Authentication Strings + */ + override val verifyPhoneNumberTitle: String + get() = localizedContext.getString(R.string.fui_verify_phone_number_title) + override val phoneHint: String + get() = localizedContext.getString(R.string.fui_phone_hint) + override val countryHint: String + get() = localizedContext.getString(R.string.fui_country_hint) + override val invalidPhoneNumber: String + get() = localizedContext.getString(R.string.fui_invalid_phone_number) + override val enterConfirmationCode: String + get() = localizedContext.getString(R.string.fui_enter_confirmation_code) + override val verifyPhoneNumber: String + get() = localizedContext.getString(R.string.fui_verify_phone_number) + override val resendCodeIn: String + get() = localizedContext.getString(R.string.fui_resend_code_in) + override val resendCode: String + get() = localizedContext.getString(R.string.fui_resend_code) + override val verifying: String + get() = localizedContext.getString(R.string.fui_verifying) + override val incorrectCodeDialogBody: String + get() = localizedContext.getString(R.string.fui_incorrect_code_dialog_body) + override val smsTermsOfService: String + get() = localizedContext.getString(R.string.fui_sms_terms_of_service) + + /** + * Multi-Factor Authentication Strings + */ + override val enterTOTPCode: String + get() = "Enter TOTP Code" + + /** + * Provider Picker Strings + */ + override val signInDefault: String + get() = localizedContext.getString(R.string.fui_sign_in_default) + override val continueText: String + get() = localizedContext.getString(R.string.fui_continue) + override val nextDefault: String + get() = localizedContext.getString(R.string.fui_next_default) + + /** + * General Error Messages + */ + override val errorUnknown: String + get() = localizedContext.getString(R.string.fui_error_unknown) + override val requiredField: String + get() = localizedContext.getString(R.string.fui_required_field) + override val progressDialogLoading: String + get() = localizedContext.getString(R.string.fui_progress_dialog_loading) + override val noInternet: String + get() = localizedContext.getString(R.string.fui_no_internet) +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt index d6b66194f..7acfc8bc1 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider internal class EmailValidator(override val stringProvider: AuthUIStringProvider) : FieldValidator { private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt index a26741897..efa72188f 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider /** * An interface for validating input fields. diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt index 35605818e..67cb7d376 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.PasswordRule internal class PasswordValidator( diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 71b87f547..1d73384e4 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -94,6 +94,7 @@ Incorrect password. + Passwords do not match Password must be at least %1$d characters long Password must contain at least one uppercase letter Password must contain at least one lowercase letter diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt index 0233848e4..95164f638 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt @@ -14,13 +14,23 @@ package com.firebase.ui.auth.compose.configuration +import android.content.Context +import android.content.res.Configuration import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.R +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider import com.google.common.truth.Truth.assertThat import com.google.firebase.auth.actionCodeSettings import org.junit.Assert.assertThrows +import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.mock +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config import java.util.Locale import kotlin.reflect.KMutableProperty import kotlin.reflect.full.memberProperties @@ -31,8 +41,17 @@ import kotlin.reflect.full.memberProperties * * @suppress Internal test class */ +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) class AuthUIConfigurationTest { + private lateinit var applicationContext: Context + + @Before + fun setUp() { + applicationContext = ApplicationProvider.getApplicationContext() + } + // ============================================================================================= // Basic Configuration Tests // ============================================================================================= @@ -40,6 +59,7 @@ class AuthUIConfigurationTest { @Test fun `authUIConfiguration with minimal setup uses correct defaults`() { val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -50,9 +70,10 @@ class AuthUIConfigurationTest { } } + assertThat(config.context).isEqualTo(applicationContext) assertThat(config.providers).hasSize(1) assertThat(config.theme).isEqualTo(AuthUITheme.Default) - assertThat(config.stringProvider).isNull() + assertThat(config.stringProvider).isInstanceOf(DefaultAuthUIStringProvider::class.java) assertThat(config.locale).isNull() assertThat(config.isCredentialManagerEnabled).isTrue() assertThat(config.isMfaEnabled).isTrue() @@ -77,6 +98,7 @@ class AuthUIConfigurationTest { } val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -105,6 +127,7 @@ class AuthUIConfigurationTest { isProviderChoiceAlwaysShown = true } + assertThat(config.context).isEqualTo(applicationContext) assertThat(config.providers).hasSize(2) assertThat(config.theme).isEqualTo(customTheme) assertThat(config.stringProvider).isEqualTo(customStringProvider) @@ -121,18 +144,146 @@ class AuthUIConfigurationTest { assertThat(config.isProviderChoiceAlwaysShown).isTrue() } + @Test + fun `providers block can be called multiple times and accumulates providers`() { + val config = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = listOf(), + serverClientId = "" + ) + ) + } + + providers { + provider( + AuthProvider.Github( + customParameters = mapOf() + ) + ) + } + isCredentialManagerEnabled = true + } + + assertThat(config.providers).hasSize(2) + } + + @Test + fun `authUIConfiguration uses custom string provider`() { + val spanishAuthUIStringProvider = + object : AuthUIStringProvider by DefaultAuthUIStringProvider(applicationContext) { + // Email Validation + override val missingEmailAddress: String = + "Ingrese su dirección de correo para continuar" + override val invalidEmailAddress: String = "Esa dirección de correo no es correcta" + + // Password Validation + override val invalidPassword: String = "Contraseña incorrecta" + override val passwordsDoNotMatch: String = "Las contraseñas no coinciden" + } + + val config = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = listOf(), + serverClientId = "" + ) + ) + } + stringProvider = spanishAuthUIStringProvider + } + + assertThat(config.stringProvider.missingEmailAddress) + .isEqualTo(spanishAuthUIStringProvider.missingEmailAddress) + } + + @Test + fun `locale set to FR in authUIConfiguration reflects in DefaultAuthUIStringProvider`() { + val localizedContext = applicationContext.createConfigurationContext( + Configuration(applicationContext.resources.configuration).apply { + setLocale(Locale.FRANCE) + } + ) + + val config = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = listOf(), + serverClientId = "" + ) + ) + } + locale = Locale.FRANCE + } + + assertThat(config.stringProvider.continueText) + .isEqualTo(localizedContext.getString(R.string.fui_continue)) + } + + @Test + fun `unsupported locale set in authUIConfiguration uses default localized strings`() { + val unsupportedLocale = Locale("zz", "ZZ") + + val config = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = listOf(), serverClientId + = "" + ) + ) + } + locale = unsupportedLocale + } + + assertThat(config.stringProvider.signInWithGoogle).isNotEmpty() + assertThat(config.stringProvider.continueText).isNotEmpty() + assertThat(config.stringProvider.signInWithGoogle) + .isEqualTo(applicationContext.getString(R.string.fui_sign_in_with_google)) + assertThat(config.stringProvider.continueText) + .isEqualTo(applicationContext.getString(R.string.fui_continue)) + } + // ============================================================================================= // Validation Tests // ============================================================================================= - @Test(expected = IllegalArgumentException::class) + @Test + fun `authUIConfiguration throws when no context configured`() { + try { + authUIConfiguration { + context = applicationContext + providers { + provider(AuthProvider.Google(scopes = listOf(), serverClientId = "")) + } + } + } catch (e: Exception) { + assertThat(e.message).isEqualTo("Application context is required") + } + } + + @Test fun `authUIConfiguration throws when no providers configured`() { - authUIConfiguration { } + try { + authUIConfiguration { + context = applicationContext + } + } catch (e: Exception) { + assertThat(e.message).isEqualTo("At least one provider must be configured") + } } @Test fun `validation accepts all supported providers`() { val config = authUIConfiguration { + context = applicationContext providers { provider(AuthProvider.Google(scopes = listOf(), serverClientId = "")) provider(AuthProvider.Facebook()) @@ -165,6 +316,7 @@ class AuthUIConfigurationTest { ) authUIConfiguration { + context = applicationContext providers { provider(mockProvider) } @@ -174,6 +326,7 @@ class AuthUIConfigurationTest { @Test(expected = IllegalStateException::class) fun `validate throws when only anonymous provider is configured`() { authUIConfiguration { + context = applicationContext providers { provider(AuthProvider.Anonymous) } @@ -183,6 +336,7 @@ class AuthUIConfigurationTest { @Test(expected = IllegalArgumentException::class) fun `validate throws for duplicate providers`() { authUIConfiguration { + context = applicationContext providers { provider(AuthProvider.Google(scopes = listOf(), serverClientId = "")) provider( @@ -198,6 +352,7 @@ class AuthUIConfigurationTest { @Test(expected = IllegalArgumentException::class) fun `validate throws for enableEmailLinkSignIn true when actionCodeSettings is null`() { authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Email( @@ -217,6 +372,7 @@ class AuthUIConfigurationTest { handleCodeInApp = false } authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Email( @@ -229,35 +385,6 @@ class AuthUIConfigurationTest { } } - // ============================================================================================= - // Provider Configuration Tests - // ============================================================================================= - - @Test - fun `providers block can be called multiple times and accumulates providers`() { - val config = authUIConfiguration { - providers { - provider( - AuthProvider.Google( - scopes = listOf(), - serverClientId = "" - ) - ) - } - - providers { - provider( - AuthProvider.Github( - customParameters = mapOf() - ) - ) - } - isCredentialManagerEnabled = true - } - - assertThat(config.providers).hasSize(2) - } - // ============================================================================================= // Builder Immutability Tests // ============================================================================================= @@ -265,6 +392,7 @@ class AuthUIConfigurationTest { @Test fun `authUIConfiguration providers list is immutable`() { val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -297,6 +425,7 @@ class AuthUIConfigurationTest { } val expectedProperties = setOf( + "context", "providers", "theme", "stringProvider", diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt index d3cacb488..a4d5139a6 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt @@ -17,6 +17,8 @@ package com.firebase.ui.auth.compose.configuration import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -34,7 +36,7 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class PasswordRuleTest { - private lateinit var stringProvider: DefaultAuthUIStringProvider + private lateinit var stringProvider: AuthUIStringProvider @Before fun setUp() { diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt index 520908181..3253cfb1c 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt @@ -17,12 +17,12 @@ package com.firebase.ui.auth.compose.configuration.validators import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -37,7 +37,7 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class EmailValidatorTest { - private lateinit var stringProvider: DefaultAuthUIStringProvider + private lateinit var stringProvider: AuthUIStringProvider private lateinit var emailValidator: EmailValidator diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt index 4e8d2e440..a3993bd36 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt @@ -17,7 +17,8 @@ package com.firebase.ui.auth.compose.configuration.validators import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.PasswordRule import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -37,7 +38,7 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class PasswordValidatorTest { - private lateinit var stringProvider: DefaultAuthUIStringProvider + private lateinit var stringProvider: AuthUIStringProvider private lateinit var passwordValidator: PasswordValidator @Before