diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/method_picker/AuthMethodPicker.kt b/auth/src/main/java/com/firebase/ui/auth/ui/method_picker/AuthMethodPicker.kt index e51c71c52..26c2feaed 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/method_picker/AuthMethodPicker.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/method_picker/AuthMethodPicker.kt @@ -88,6 +88,9 @@ class MethodPickerTermsConfiguration( * @param lastSignInPreference The last sign-in preference to show a "Continue as..." button. * @param termsConfiguration Optional configuration for a custom ToS/Privacy Policy footer. * When provided, replaces the default "By continuing..." text. See [MethodPickerTermsConfiguration]. + * @param onContinueAsSelected A callback when the "Continue as..." button is selected, with the + * provider and saved identifier (email address). Falls back to [onProviderSelected] + * if not provided. * * @since 10.0.0 */ @@ -102,7 +105,10 @@ fun AuthMethodPicker( lastSignInPreference: SignInPreferenceManager.SignInPreference? = null, customLayout: (@Composable (List, (AuthProvider) -> Unit) -> Unit)? = null, termsConfiguration: MethodPickerTermsConfiguration? = null, + onContinueAsSelected: ((AuthProvider, String?) -> Unit)? = null, ) { + val continueAsHandler: (AuthProvider, String?) -> Unit = + onContinueAsSelected ?: { provider, _ -> onProviderSelected(provider) } val context = LocalContext.current val inPreview = LocalInspectionMode.current val stringProvider = LocalAuthUIStringProvider.current @@ -150,7 +156,7 @@ fun AuthMethodPicker( provider = lastProvider, identifier = preference.identifier, enabled = providerButtonsEnabled, - onClick = { onProviderSelected(lastProvider) } + onClick = { continueAsHandler(lastProvider, preference.identifier) } ) Spacer(modifier = Modifier.height(24.dp)) @@ -218,7 +224,7 @@ fun AuthMethodPicker( * A prominent "Continue as..." button that shows the last-used provider and identifier. * * @param provider The authentication provider - * @param identifier The user identifier (email, phone number, etc.) + * @param identifier The user identifier (email address) * @param onClick Callback when the button is clicked */ @Composable diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt index 2d40e98e7..c2d78b8d5 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt @@ -156,6 +156,7 @@ fun FirebaseAuthScreen( val pendingReauthState = remember { mutableStateOf(null) } val pendingReauthOperation = remember { mutableStateOf<(suspend (android.content.Context) -> Unit)?>(null) } val emailLinkFromDifferentDevice = remember { mutableStateOf(null) } + val prefillEmail = remember { mutableStateOf(null) } val lastSignInPreference = remember { mutableStateOf(null) } val startRoute = remember(configuration.providers, configuration.isProviderChoiceAlwaysShown) { @@ -231,7 +232,14 @@ fun FirebaseAuthScreen( privacyPolicyUrl = configuration.privacyPolicyUrl, lastSignInPreference = lastSignInPreference.value, termsConfiguration = customMethodPickerTermsConfiguration, - onProviderSelected = onProviderSelected, + onProviderSelected = { provider -> + prefillEmail.value = null + onProviderSelected(provider) + }, + onContinueAsSelected = { provider, identifier -> + prefillEmail.value = if (provider is AuthProvider.Email) identifier else null + onProviderSelected(provider) + }, ) } } @@ -242,6 +250,7 @@ fun FirebaseAuthScreen( context = context, configuration = configuration, authUI = authUI, + prefillEmail = prefillEmail.value, credentialForLinking = pendingLinkingCredential.value, emailLinkFromDifferentDevice = emailLinkFromDifferentDevice.value, onContinueWithProvider = continueWithProvider, diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt index 667fc364b..cb4d8e05e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt @@ -135,6 +135,7 @@ fun EmailAuthScreen( onSuccess: (AuthResult) -> Unit, onError: (AuthException) -> Unit, onCancel: () -> Unit, + prefillEmail: String? = null, content: @Composable ((EmailAuthContentState) -> Unit)? = null, ) { val provider = configuration.providers.filterIsInstance().first() @@ -150,7 +151,7 @@ fun EmailAuthScreen( } val mode = rememberSaveable { mutableStateOf(initialMode) } val displayNameValue = rememberSaveable { mutableStateOf("") } - val emailTextValue = rememberSaveable { mutableStateOf("") } + val emailTextValue = rememberSaveable { mutableStateOf(prefillEmail ?: "") } val passwordTextValue = rememberSaveable { mutableStateOf("") } val confirmPasswordTextValue = rememberSaveable { mutableStateOf("") } diff --git a/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignInUITest.kt b/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignInUITest.kt new file mode 100644 index 000000000..1b016419d --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignInUITest.kt @@ -0,0 +1,119 @@ +/* + * 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.ui.screens.email + +import android.content.Context +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.test.assertTextContains +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.configuration.authUIConfiguration +import com.firebase.ui.auth.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class SignInUITest { + + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var applicationContext: Context + private lateinit var stringProvider: AuthUIStringProvider + + @Before + fun setUp() { + applicationContext = ApplicationProvider.getApplicationContext() + stringProvider = DefaultAuthUIStringProvider(applicationContext) + } + + @Test + fun `email field is pre-filled when initial email value is provided`() { + val prefillEmail = "user@example.com" + val provider = AuthProvider.Email( + isDisplayNameRequired = false, + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + val configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + } + + composeTestRule.setContent { + CompositionLocalProvider(LocalAuthUIStringProvider provides stringProvider) { + SignInUI( + configuration = configuration, + isLoading = false, + emailSignInLinkSent = false, + email = prefillEmail, + password = "", + onEmailChange = { }, + onPasswordChange = { }, + onRetrievedCredential = { }, + onSignInClick = { }, + onGoToSignUp = { }, + onGoToResetPassword = { }, + onGoToEmailLinkSignIn = { }, + ) + } + } + + composeTestRule.onNodeWithText(prefillEmail).assertExists() + } + + @Test + fun `email field is empty when no initial email value is provided`() { + val provider = AuthProvider.Email( + isDisplayNameRequired = false, + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + val configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + } + + composeTestRule.setContent { + CompositionLocalProvider(LocalAuthUIStringProvider provides stringProvider) { + SignInUI( + configuration = configuration, + isLoading = false, + emailSignInLinkSent = false, + email = "", + password = "", + onEmailChange = { }, + onPasswordChange = { }, + onRetrievedCredential = { }, + onSignInClick = { }, + onGoToSignUp = { }, + onGoToResetPassword = { }, + onGoToEmailLinkSignIn = { }, + ) + } + } + + composeTestRule.onNodeWithText("user@example.com").assertDoesNotExist() + } +}