From ea1c2fb2a989a769035e9dfa78f36ba51b33bcf0 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Mon, 22 Sep 2025 14:30:26 +0100 Subject: [PATCH 1/4] feat: added context AuthUIConfiguration and default string provider --- .../configuration/AuthUIConfiguration.kt | 81 +++++++------- .../configuration/AuthUIStringProvider.kt | 100 +++++++++++++++--- auth/src/main/res/values/strings.xml | 1 + .../configuration/AuthUIConfigurationTest.kt | 54 +++++++++- .../compose/configuration/PasswordRuleTest.kt | 2 +- .../validators/EmailValidatorTest.kt | 3 +- .../validators/PasswordValidatorTest.kt | 3 +- 7 files changed, 187 insertions(+), 57 deletions(-) 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..23965ac7c 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,27 @@ 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 -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 +46,18 @@ 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 - ) - } + // Context is not null + 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 index 0e7080722..82d69b40c 100644 --- 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 @@ -15,7 +15,9 @@ package com.firebase.ui.auth.compose.configuration import android.content.Context +import android.content.res.Configuration import com.firebase.ui.auth.R +import java.util.Locale /** * An interface for providing localized string resources. This interface defines methods for all @@ -29,6 +31,33 @@ interface AuthUIStringProvider { /** 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 @@ -57,25 +86,72 @@ interface AuthUIStringProvider { val passwordMissingSpecialCharacter: String } -internal class DefaultAuthUIStringProvider(private val context: Context) : AuthUIStringProvider { - override val initializing: String get() = "" +internal class DefaultAuthUIStringProvider( + private val context: Context, + private val locale: Locale? = null, +) : AuthUIStringProvider { + + private val localizedContext = locale?.let { locale -> + context.createConfigurationContext( + Configuration(context.resources.configuration).apply { + setLocale(locale) + } + ) + } ?: context + + /** + * General Strings + */ + override val initializing: String + get() = "" + + /** + * Auth Provider Button Strings + */ override val signInWithGoogle: String - get() = context.getString(R.string.fui_sign_in_with_google) + 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() = context.getString(R.string.fui_missing_email_address) + get() = localizedContext.getString(R.string.fui_missing_email_address) override val invalidEmailAddress: String - get() = context.getString(R.string.fui_invalid_email_address) + get() = localizedContext.getString(R.string.fui_invalid_email_address) + + /** + * Password Validator Strings + */ override val invalidPassword: String - get() = context.getString(R.string.fui_error_invalid_password) - override val passwordsDoNotMatch: String get() = "" + 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() = context.getString(R.string.fui_error_password_too_short) + get() = localizedContext.getString(R.string.fui_error_password_too_short) override val passwordMissingUppercase: String - get() = context.getString(R.string.fui_error_password_missing_uppercase) + get() = localizedContext.getString(R.string.fui_error_password_missing_uppercase) override val passwordMissingLowercase: String - get() = context.getString(R.string.fui_error_password_missing_lowercase) + get() = localizedContext.getString(R.string.fui_error_password_missing_lowercase) override val passwordMissingDigit: String - get() = context.getString(R.string.fui_error_password_missing_digit) + get() = localizedContext.getString(R.string.fui_error_password_missing_digit) override val passwordMissingSpecialCharacter: String - get() = context.getString(R.string.fui_error_password_missing_special_character) + get() = localizedContext.getString(R.string.fui_error_password_missing_special_character) } 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..070d8703b 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,19 @@ package com.firebase.ui.auth.compose.configuration +import android.content.Context import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle +import androidx.test.core.app.ApplicationProvider 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 +37,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 +55,7 @@ class AuthUIConfigurationTest { @Test fun `authUIConfiguration with minimal setup uses correct defaults`() { val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -50,9 +66,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 +94,7 @@ class AuthUIConfigurationTest { } val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -105,6 +123,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) @@ -125,14 +144,35 @@ class AuthUIConfigurationTest { // 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 +205,7 @@ class AuthUIConfigurationTest { ) authUIConfiguration { + context = applicationContext providers { provider(mockProvider) } @@ -174,6 +215,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 +225,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 +241,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 +261,7 @@ class AuthUIConfigurationTest { handleCodeInApp = false } authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Email( @@ -236,6 +281,7 @@ class AuthUIConfigurationTest { @Test fun `providers block can be called multiple times and accumulates providers`() { val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -265,6 +311,7 @@ class AuthUIConfigurationTest { @Test fun `authUIConfiguration providers list is immutable`() { val config = authUIConfiguration { + context = applicationContext providers { provider( AuthProvider.Google( @@ -297,6 +344,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..b8a9a67f2 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 @@ -34,7 +34,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..e1ec384a8 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,6 +17,7 @@ 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.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.DefaultAuthUIStringProvider 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 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..f904d7ba8 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,6 +17,7 @@ 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.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.PasswordRule import com.google.common.truth.Truth.assertThat @@ -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 From 7f29a25b77ba710d6c6692c0ced23e3f55968fe6 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Mon, 22 Sep 2025 16:10:46 +0100 Subject: [PATCH 2/4] add/expose existing localized strings to allow overrides --- .../configuration/AuthUIStringProvider.kt | 201 +++++++++++++++++- 1 file changed, 198 insertions(+), 3 deletions(-) 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 index 82d69b40c..08e7cf1e9 100644 --- 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 @@ -21,13 +21,31 @@ import java.util.Locale /** * An interface for providing localized string resources. This interface defines methods for all - * user-facing strings, such as initializing(), signInWithGoogle(), invalidEmail(), + * user-facing strings, such as initializing(), signInWithGoogle(), invalidEmailAddress(), * passwordsDoNotMatch(), etc., allowing for complete localization of the UI. */ 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 @@ -84,13 +102,100 @@ interface AuthUIStringProvider { /** 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 } internal 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 { @@ -100,11 +205,27 @@ internal class DefaultAuthUIStringProvider( } ?: context /** - * General Strings + * Common Strings */ override val initializing: String get() = "" + /** + * 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 */ @@ -154,4 +275,78 @@ internal class DefaultAuthUIStringProvider( 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() = "" + + /** + * 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) } From f74c2a1b5e26afe9e99033821f895e7460463353 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Mon, 22 Sep 2025 23:27:34 +0100 Subject: [PATCH 3/4] added custom string provider sample, tests for locale overrides --- .../configuration/AuthUIConfiguration.kt | 2 + .../compose/configuration/PasswordRule.kt | 2 + .../stringprovider/AuthUIStringProvider.kt | 187 ++++++++++++++++++ .../AuthUIStringProviderSample.kt | 59 ++++++ .../DefaultAuthUIStringProvider.kt} | 178 +---------------- .../validators/EmailValidator.kt | 2 +- .../validators/FieldValidator.kt | 2 +- .../validators/PasswordValidator.kt | 2 +- .../configuration/AuthUIConfigurationTest.kt | 141 ++++++++++--- .../compose/configuration/PasswordRuleTest.kt | 2 + .../validators/EmailValidatorTest.kt | 5 +- .../validators/PasswordValidatorTest.kt | 4 +- 12 files changed, 374 insertions(+), 212 deletions(-) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt rename auth/src/main/java/com/firebase/ui/auth/compose/configuration/{AuthUIStringProvider.kt => stringprovider/DefaultAuthUIStringProvider.kt} (60%) 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 23965ac7c..9a901979f 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 @@ -18,6 +18,8 @@ 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() 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/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt similarity index 60% rename from auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt index 08e7cf1e9..96a74cdd7 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt @@ -12,184 +12,14 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose.configuration +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 -/** - * 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. - */ -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 -} - -internal class DefaultAuthUIStringProvider( +class DefaultAuthUIStringProvider( private val context: Context, private val locale: Locale? = null, ) : AuthUIStringProvider { @@ -208,7 +38,7 @@ internal class DefaultAuthUIStringProvider( * Common Strings */ override val initializing: String - get() = "" + get() = "Initializing" /** * Auth Provider strings @@ -326,7 +156,7 @@ internal class DefaultAuthUIStringProvider( * Multi-Factor Authentication Strings */ override val enterTOTPCode: String - get() = "" + get() = "Enter TOTP Code" /** * Provider Picker Strings 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/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt index 070d8703b..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 @@ -15,9 +15,13 @@ 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 @@ -140,6 +144,113 @@ 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 // ============================================================================================= @@ -274,36 +385,6 @@ class AuthUIConfigurationTest { } } - // ============================================================================================= - // Provider Configuration Tests - // ============================================================================================= - - @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) - } - // ============================================================================================= // Builder Immutability Tests // ============================================================================================= 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 b8a9a67f2..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 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 e1ec384a8..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,13 +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.AuthUIStringProvider -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 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 f904d7ba8..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,8 +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.AuthUIStringProvider -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 From 92cd25ec1bbfbfb6c65d35960244dc1313ed336a Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 23 Sep 2025 09:22:13 +0100 Subject: [PATCH 4/4] chore: code cleanup --- .../ui/auth/compose/configuration/AuthUIConfiguration.kt | 2 -- 1 file changed, 2 deletions(-) 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 9a901979f..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 @@ -52,12 +52,10 @@ class AuthUIConfigurationBuilder { providers.addAll(AuthProvidersBuilder().apply(block).build()) internal fun build(): AuthUIConfiguration { - // Context is not null val context = requireNotNull(context) { "Application context is required" } - // At least one provider require(providers.isNotEmpty()) { "At least one provider must be configured" }