diff --git a/app/src/main/java/com/firebaseui/android/demo/CustomSlotsThemingDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/CustomSlotsThemingDemoActivity.kt
index 79ed7068a..00b0054f0 100644
--- a/app/src/main/java/com/firebaseui/android/demo/CustomSlotsThemingDemoActivity.kt
+++ b/app/src/main/java/com/firebaseui/android/demo/CustomSlotsThemingDemoActivity.kt
@@ -25,6 +25,9 @@ import com.firebase.ui.auth.configuration.authUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
import com.firebase.ui.auth.configuration.theme.AuthUITheme
+import com.firebase.ui.auth.configuration.theme.ProviderStyleDefaults
+import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
+import com.firebase.ui.auth.ui.components.AuthProviderButton
import com.firebase.ui.auth.ui.screens.email.EmailAuthContentState
import com.firebase.ui.auth.ui.screens.email.EmailAuthMode
import com.firebase.ui.auth.ui.screens.email.EmailAuthScreen
@@ -37,6 +40,7 @@ import com.google.firebase.auth.AuthResult
* Demo activity showcasing custom slots and theming capabilities:
* - EmailAuthScreen with custom slot UI
* - PhoneAuthScreen with custom slot UI
+ * - Provider button shape customization with global and per-provider overrides
* - AuthUITheme.fromMaterialTheme() with custom ProviderStyle overrides
*/
class CustomSlotsThemingDemoActivity : ComponentActivity() {
@@ -121,6 +125,7 @@ class CustomSlotsThemingDemoActivity : ComponentActivity() {
configuration = phoneConfiguration,
context = appContext
)
+ DemoType.ShapeCustomization -> ShapeCustomizationDemo()
}
}
}
@@ -131,43 +136,20 @@ class CustomSlotsThemingDemoActivity : ComponentActivity() {
enum class DemoType {
Email,
- Phone
+ Phone,
+ ShapeCustomization
}
@Composable
fun CustomAuthUITheme(content: @Composable () -> Unit) {
// Use Material Theme colors
MaterialTheme {
- val customProviderStyles = mapOf(
- "google.com" to AuthUITheme.ProviderStyle(
- icon = null, // Would use actual Google icon in production
- backgroundColor = Color(0xFFFFFFFF),
- contentColor = Color(0xFF757575),
- iconTint = null,
- shape = RoundedCornerShape(8.dp),
- elevation = 1.dp
- ),
- "facebook.com" to AuthUITheme.ProviderStyle(
- icon = null, // Would use actual Facebook icon in production
- backgroundColor = Color(0xFF1877F2),
- contentColor = Color.White,
- iconTint = null,
- shape = RoundedCornerShape(8.dp),
- elevation = 2.dp
- ),
- "password" to AuthUITheme.ProviderStyle(
- icon = null,
- backgroundColor = MaterialTheme.colorScheme.primary,
- contentColor = MaterialTheme.colorScheme.onPrimary,
- iconTint = null,
- shape = RoundedCornerShape(12.dp),
- elevation = 3.dp
- )
+ // UPDATED: Now uses ProviderStyleDefaults and the new providerButtonShape API
+ // Apply custom theme using fromMaterialTheme with global button shape
+ val authTheme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(12.dp) // Global shape for all buttons
)
- // Apply custom theme using fromMaterialTheme
- val authTheme = AuthUITheme.fromMaterialTheme(providerStyles = customProviderStyles)
-
AuthUITheme(theme = authTheme) {
content()
}
@@ -202,21 +184,32 @@ fun DemoSelector(
color = MaterialTheme.colorScheme.onPrimaryContainer
)
- Row(
+ Column(
modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(8.dp)
+ verticalArrangement = Arrangement.spacedBy(8.dp)
) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ FilterChip(
+ selected = selectedDemo == DemoType.Email,
+ onClick = { onDemoSelected(DemoType.Email) },
+ label = { Text("Email Auth") },
+ modifier = Modifier.weight(1f)
+ )
+ FilterChip(
+ selected = selectedDemo == DemoType.Phone,
+ onClick = { onDemoSelected(DemoType.Phone) },
+ label = { Text("Phone Auth") },
+ modifier = Modifier.weight(1f)
+ )
+ }
FilterChip(
- selected = selectedDemo == DemoType.Email,
- onClick = { onDemoSelected(DemoType.Email) },
- label = { Text("Email Auth") },
- modifier = Modifier.weight(1f)
- )
- FilterChip(
- selected = selectedDemo == DemoType.Phone,
- onClick = { onDemoSelected(DemoType.Phone) },
- label = { Text("Phone Auth") },
- modifier = Modifier.weight(1f)
+ selected = selectedDemo == DemoType.ShapeCustomization,
+ onClick = { onDemoSelected(DemoType.ShapeCustomization) },
+ label = { Text("Shape Customization") },
+ modifier = Modifier.fillMaxWidth()
)
}
}
@@ -823,3 +816,290 @@ fun EnterVerificationCodeUI(state: PhoneAuthContentState) {
}
}
}
+
+/**
+ * Demo showcasing provider button shape customization capabilities.
+ * Demonstrates:
+ * - Global shape configuration for all buttons
+ * - Per-provider shape overrides
+ * - Using ProviderStyleDefaults with .copy()
+ */
+@Composable
+fun ShapeCustomizationDemo() {
+ val context = androidx.compose.ui.platform.LocalContext.current
+ val stringProvider = DefaultAuthUIStringProvider(context)
+ var selectedPreset by remember { mutableStateOf(ShapePreset.DEFAULT) }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Title and description
+ Text(
+ text = "Provider Button Shape Customization",
+ style = MaterialTheme.typography.headlineSmall,
+ color = MaterialTheme.colorScheme.primary
+ )
+
+ Text(
+ text = "This demo showcases the new shape customization API for provider buttons. " +
+ "You can set a global shape for all buttons or customize individual providers.",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+
+ HorizontalDivider()
+
+ // Preset selector
+ Text(
+ text = "Select Shape Preset:",
+ style = MaterialTheme.typography.titleMedium
+ )
+
+ ShapePreset.entries.forEach { preset ->
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
+ selected = selectedPreset == preset,
+ onClick = { selectedPreset = preset }
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Column {
+ Text(
+ text = preset.displayName,
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Text(
+ text = preset.description,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ }
+ }
+
+ HorizontalDivider()
+
+ // Preview section
+ Text(
+ text = "Preview:",
+ style = MaterialTheme.typography.titleMedium
+ )
+
+ // Render buttons with the selected preset
+ when (selectedPreset) {
+ ShapePreset.DEFAULT -> DefaultShapeButtons(stringProvider)
+ ShapePreset.DEFAULT_COPY -> DefaultCopyShapeButtons(stringProvider)
+ ShapePreset.DARK_COPY -> DarkCopyShapeButtons(stringProvider)
+ ShapePreset.FROM_MATERIAL -> FromMaterialThemeButtons(stringProvider)
+ ShapePreset.PILL -> PillShapeButtons(stringProvider)
+ ShapePreset.MIXED -> MixedShapeButtons(stringProvider)
+ }
+
+ // Code example
+ HorizontalDivider()
+
+ Text(
+ text = "Code Example:",
+ style = MaterialTheme.typography.titleMedium
+ )
+
+ Surface(
+ modifier = Modifier.fillMaxWidth(),
+ color = MaterialTheme.colorScheme.surfaceVariant,
+ shape = RoundedCornerShape(8.dp)
+ ) {
+ Text(
+ text = selectedPreset.codeExample,
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
+ ),
+ modifier = Modifier.padding(12.dp)
+ )
+ }
+ }
+}
+
+enum class ShapePreset(
+ val displayName: String,
+ val description: String,
+ val codeExample: String
+) {
+ DEFAULT(
+ "Default Shapes",
+ "Uses the standard 4dp rounded corners",
+ """
+// No customization needed
+val theme = AuthUITheme.Default
+ """.trimIndent()
+ ),
+ DEFAULT_COPY(
+ "Default.copy()",
+ "Customize default light theme with .copy()",
+ """
+val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp)
+)
+ """.trimIndent()
+ ),
+ DARK_COPY(
+ "DefaultDark.copy()",
+ "Customize default dark theme with .copy()",
+ """
+val theme = AuthUITheme.DefaultDark.copy(
+ providerButtonShape = RoundedCornerShape(16.dp)
+)
+ """.trimIndent()
+ ),
+ FROM_MATERIAL(
+ "fromMaterialTheme()",
+ "Inherit from Material Theme",
+ """
+val theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(12.dp)
+)
+ """.trimIndent()
+ ),
+ PILL(
+ "Pill Shape",
+ "Creates pill-shaped buttons (Default.copy)",
+ """
+val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(28.dp)
+)
+ """.trimIndent()
+ ),
+ MIXED(
+ "Mixed Shapes",
+ "Different shapes per provider (Default.copy)",
+ """
+val customStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = RoundedCornerShape(24.dp)
+ ),
+ "facebook.com" to ProviderStyleDefaults.Facebook.copy(
+ shape = RoundedCornerShape(8.dp)
+ )
+)
+
+val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp),
+ providerStyles = customStyles
+)
+ """.trimIndent()
+ )
+}
+
+@Composable
+fun DefaultShapeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Default theme - no customization
+ AuthUITheme {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun DefaultCopyShapeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Using AuthUITheme.Default.copy() to customize the light theme
+ val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp)
+ )
+ AuthUITheme(theme = theme) {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun DarkCopyShapeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Using AuthUITheme.DefaultDark.copy() to customize the dark theme
+ val theme = AuthUITheme.DefaultDark.copy(
+ providerButtonShape = RoundedCornerShape(16.dp)
+ )
+ AuthUITheme(theme = theme) {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun FromMaterialThemeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Using AuthUITheme.fromMaterialTheme() to inherit from Material Theme
+ val theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(12.dp)
+ )
+ AuthUITheme(theme = theme) {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun PillShapeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Pill-shaped buttons using Default.copy()
+ val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(28.dp)
+ )
+ AuthUITheme(theme = theme) {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun MixedShapeButtons(stringProvider: DefaultAuthUIStringProvider) {
+ // Mixed shapes per provider using Default.copy()
+ val customStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = RoundedCornerShape(24.dp) // Pill shape for Google
+ ),
+ "facebook.com" to ProviderStyleDefaults.Facebook.copy(
+ shape = RoundedCornerShape(8.dp) // Medium rounded for Facebook
+ )
+ // Email uses global default (12dp)
+ )
+
+ val theme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp),
+ providerStyles = customStyles
+ )
+
+ AuthUITheme(theme = theme) {
+ ButtonPreviewColumn(stringProvider)
+ }
+}
+
+@Composable
+fun ButtonPreviewColumn(stringProvider: DefaultAuthUIStringProvider) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ AuthProviderButton(
+ provider = AuthProvider.Google(scopes = emptyList(), serverClientId = null),
+ onClick = { },
+ stringProvider = stringProvider,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ AuthProviderButton(
+ provider = AuthProvider.Facebook(),
+ onClick = { },
+ stringProvider = stringProvider,
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ AuthProviderButton(
+ provider = AuthProvider.Email(
+ emailLinkActionCodeSettings = null,
+ passwordValidationRules = emptyList()
+ ),
+ onClick = { },
+ stringProvider = stringProvider,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+}
diff --git a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt
index 570f6135f..52427bba9 100644
--- a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt
+++ b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt
@@ -5,6 +5,8 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -13,6 +15,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ShapeDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -24,6 +27,7 @@ import androidx.compose.ui.unit.dp
import com.firebase.ui.auth.AuthException
import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
+import com.firebase.ui.auth.configuration.AuthUITransitions
import com.firebase.ui.auth.configuration.PasswordRule
import com.firebase.ui.auth.configuration.authUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
@@ -42,12 +46,23 @@ class HighLevelApiDemoActivity : ComponentActivity() {
val authUI = FirebaseAuthUI.getInstance()
val emailLink = intent.getStringExtra(EmailLinkConstants.EXTRA_EMAIL_LINK)
+ val customTheme = AuthUITheme.Default.copy(
+ providerButtonShape = ShapeDefaults.ExtraLarge
+ )
+
val configuration = authUIConfiguration {
context = applicationContext
+ theme = customTheme
logo = AuthUIAsset.Resource(R.drawable.firebase_auth)
tosUrl = "https://policies.google.com/terms"
privacyPolicyUrl = "https://policies.google.com/privacy"
isAnonymousUpgradeEnabled = false
+ transitions = AuthUITransitions(
+ enterTransition = { slideInHorizontally { it } },
+ exitTransition = { slideOutHorizontally { -it } },
+ popEnterTransition = { slideInHorizontally { -it } },
+ popExitTransition = { slideOutHorizontally { it } }
+ )
providers {
provider(AuthProvider.Anonymous)
provider(
diff --git a/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt b/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
index a22e6028a..5a19beac4 100644
--- a/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
+++ b/app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
@@ -36,7 +36,7 @@ import com.google.firebase.FirebaseApp
*/
class MainActivity : ComponentActivity() {
companion object {
- private const val USE_AUTH_EMULATOR = false
+ private const val USE_AUTH_EMULATOR = true
private const val AUTH_EMULATOR_HOST = "10.0.2.2"
private const val AUTH_EMULATOR_PORT = 9099
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 515bb8e83..b0b680c7f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,10 +1,10 @@
FirebaseUI Demo
- flutterfire-e2e-tests.firebaseapp.com
+ CHANGE-HERE
- 128693022464535
- fb128693022464535
- 16dbbdf0cfb309034a6ad98ac2a21688
+ APP-ID
+ fbAPP-ID
+ CHANGE-HERE
\ No newline at end of file
diff --git a/auth/README.md b/auth/README.md
index e44ac7b6f..ecc3208aa 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -16,48 +16,62 @@ Equivalent FirebaseUI libraries are available for [iOS](https://github.com/fireb
## Table of Contents
+### Getting Started
1. [Demo](#demo)
-1. [Setup](#setup)
- 1. [Prerequisites](#prerequisites)
- 1. [Installation](#installation)
- 1. [Provider Configuration](#provider-configuration)
-1. [Quick Start](#quick-start)
- 1. [Minimal Example](#minimal-example)
- 1. [Check Authentication State](#check-authentication-state)
-1. [Core Concepts](#core-concepts)
- 1. [FirebaseAuthUI](#firebaseauthui)
- 1. [AuthUIConfiguration](#authuiconfiguration)
- 1. [AuthFlowController](#authflowcontroller)
- 1. [AuthState](#authstate)
-1. [Authentication Methods](#authentication-methods)
- 1. [Email & Password](#email--password)
- 1. [Phone Number](#phone-number)
- 1. [Google Sign-In](#google-sign-in)
- 1. [Facebook Login](#facebook-login)
- 1. [Other OAuth Providers](#other-oauth-providers)
- 1. [Anonymous Authentication](#anonymous-authentication)
- 1. [Custom OAuth Provider](#custom-oauth-provider)
-1. [Usage Patterns](#usage-patterns)
- 1. [High-Level API (Recommended)](#high-level-api-recommended)
- 1. [Low-Level API (Advanced)](#low-level-api-advanced)
- 1. [Custom UI with Slots](#custom-ui-with-slots)
-1. [Multi-Factor Authentication](#multi-factor-authentication)
- 1. [MFA Configuration](#mfa-configuration)
- 1. [MFA Enrollment](#mfa-enrollment)
- 1. [MFA Challenge](#mfa-challenge)
-1. [Theming & Customization](#theming--customization)
- 1. [Material Theme Integration](#material-theme-integration)
- 1. [Custom Theme](#custom-theme)
- 1. [Provider Button Styling](#provider-button-styling)
-1. [Advanced Features](#advanced-features)
- 1. [Anonymous User Upgrade](#anonymous-user-upgrade)
- 1. [Email Link Sign-In](#email-link-sign-in)
- 1. [Password Validation Rules](#password-validation-rules)
- 1. [Credential Manager Integration](#credential-manager-integration)
- 1. [Sign Out & Account Deletion](#sign-out--account-deletion)
-1. [Localization](#localization)
-1. [Error Handling](#error-handling)
-1. [Migration Guide](#migration-guide)
+2. [Setup](#setup)
+ - [Prerequisites](#prerequisites)
+ - [Installation](#installation)
+ - [Provider Configuration](#provider-configuration)
+3. [Quick Start](#quick-start)
+ - [Minimal Example](#minimal-example)
+ - [Check Authentication State](#check-authentication-state)
+
+### Core Concepts
+4. [Core Concepts](#core-concepts)
+ - [FirebaseAuthUI](#firebaseauthui)
+ - [AuthUIConfiguration](#authuiconfiguration)
+ - [AuthFlowController](#authflowcontroller)
+ - [AuthState](#authstate)
+
+### Authentication
+5. [Authentication Methods](#authentication-methods)
+ - [Email & Password](#email--password)
+ - [Phone Number](#phone-number)
+ - [Google Sign-In](#google-sign-in)
+ - [Facebook Login](#facebook-login)
+ - [Other OAuth Providers](#other-oauth-providers)
+ - [Anonymous Authentication](#anonymous-authentication)
+ - [Custom OAuth Provider](#custom-oauth-provider)
+6. [Multi-Factor Authentication](#multi-factor-authentication)
+ - [MFA Configuration](#mfa-configuration)
+ - [MFA Enrollment](#mfa-enrollment)
+ - [MFA Challenge](#mfa-challenge)
+
+### Implementation
+7. [Usage Patterns](#usage-patterns)
+ - [High-Level API (Recommended)](#high-level-api-recommended)
+ - [Low-Level API (Advanced)](#low-level-api-advanced)
+ - [Custom UI with Slots](#custom-ui-with-slots)
+8. [Theming & Customization](#theming--customization)
+ - [Using Default Themes](#using-default-themes)
+ - [Using Adaptive Theme](#using-adaptive-theme-recommended)
+ - [Customizing Default Theme](#customizing-default-theme)
+ - [Theme Behavior & Patterns](#theme-behavior--patterns)
+ - [Inheriting from Material Theme](#inheriting-from-material-theme)
+ - [Creating a Completely Custom Theme](#creating-a-completely-custom-theme)
+ - [Provider Button Styling](#provider-button-styling)
+ - [Screen Transitions](#screen-transitions)
+
+### Advanced
+9. [Advanced Features](#advanced-features)
+ - [Anonymous User Upgrade](#anonymous-user-upgrade)
+ - [Email Link Sign-In](#email-link-sign-in)
+ - [Password Validation Rules](#password-validation-rules)
+ - [Credential Manager Integration](#credential-manager-integration)
+ - [Sign Out & Account Deletion](#sign-out--account-deletion)
+10. [Localization](#localization)
+11. [Error Handling](#error-handling)
+12. [Migration Guide](#migration-guide)
## Demo
@@ -147,10 +161,10 @@ class MainActivity : ComponentActivity() {
setContent {
MyAppTheme {
val configuration = authUIConfiguration {
- providers = listOf(
- AuthProvider.Email(),
- AuthProvider.Google()
- )
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
}
FirebaseAuthScreen(
@@ -263,12 +277,12 @@ val authUI = FirebaseAuthUI.create(auth = customAuth)
```kotlin
val configuration = authUIConfiguration {
- // Required: List of authentication providers
- providers = listOf(
- AuthProvider.Email(),
- AuthProvider.Google(),
- AuthProvider.Phone()
- )
+ // Required: Authentication providers
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Phone())
+ }
// Optional: Theme configuration
theme = AuthUITheme.fromMaterialTheme()
@@ -353,12 +367,19 @@ override fun onDestroy() {
sealed class AuthState {
object Idle : AuthState()
data class Loading(val message: String?) : AuthState()
- data class Success(val result: AuthResult, val isNewUser: Boolean) : AuthState()
+ data class Success(val result: AuthResult?, val user: FirebaseUser, val isNewUser: Boolean = false) : AuthState()
data class Error(val exception: AuthException, val isRecoverable: Boolean) : AuthState()
- data class RequiresMfa(val resolver: MultiFactorResolver) : AuthState()
- data class RequiresEmailVerification(val user: FirebaseUser) : AuthState()
- data class RequiresProfileCompletion(val user: FirebaseUser) : AuthState()
+ data class RequiresMfa(val resolver: MultiFactorResolver, val hint: String? = null) : AuthState()
+ data class RequiresEmailVerification(val user: FirebaseUser, val email: String) : AuthState()
+ data class RequiresProfileCompletion(val user: FirebaseUser, val missingFields: List = emptyList()) : AuthState()
object Cancelled : AuthState()
+ object PasswordResetLinkSent : AuthState()
+ object EmailSignInLinkSent : AuthState()
+ data class SMSAutoVerified(val credential: PhoneAuthCredential) : AuthState()
+ data class PhoneNumberVerificationRequired(
+ val verificationId: String,
+ val forceResendingToken: PhoneAuthProvider.ForceResendingToken
+ ) : AuthState()
}
```
@@ -433,7 +454,9 @@ val phoneProvider = AuthProvider.Phone(
)
val configuration = authUIConfiguration {
- providers = listOf(phoneProvider)
+ providers {
+ provider(phoneProvider)
+ }
}
```
@@ -454,7 +477,9 @@ val googleProvider = AuthProvider.Google(
)
val configuration = authUIConfiguration {
- providers = listOf(googleProvider)
+ providers {
+ provider(googleProvider)
+ }
}
```
@@ -475,7 +500,9 @@ val facebookProvider = AuthProvider.Facebook(
)
val configuration = authUIConfiguration {
- providers = listOf(facebookProvider)
+ providers {
+ provider(facebookProvider)
+ }
}
```
@@ -533,13 +560,13 @@ val appleProvider = AuthProvider.Apple(
)
val configuration = authUIConfiguration {
- providers = listOf(
- twitterProvider,
- githubProvider,
- microsoftProvider,
- yahooProvider,
- appleProvider
- )
+ providers {
+ provider(twitterProvider)
+ provider(githubProvider)
+ provider(microsoftProvider)
+ provider(yahooProvider)
+ provider(appleProvider)
+ }
}
```
@@ -549,9 +576,9 @@ Enable anonymous authentication to let users use your app without signing in:
```kotlin
val configuration = authUIConfiguration {
- providers = listOf(
- AuthProvider.Anonymous()
- )
+ providers {
+ provider(AuthProvider.Anonymous())
+ }
// Enable anonymous user upgrade
isAnonymousUpgradeEnabled = true
@@ -590,7 +617,9 @@ val lineProvider = AuthProvider.GenericOAuth(
)
val configuration = authUIConfiguration {
- providers = listOf(lineProvider)
+ providers {
+ provider(lineProvider)
+ }
}
```
@@ -604,12 +633,12 @@ The high-level API provides a complete, opinionated authentication experience wi
@Composable
fun AuthenticationScreen() {
val configuration = authUIConfiguration {
- providers = listOf(
- AuthProvider.Email(),
- AuthProvider.Google(),
- AuthProvider.Facebook(),
- AuthProvider.Phone()
- )
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ provider(AuthProvider.Phone())
+ }
tosUrl = "https://example.com/terms"
privacyPolicyUrl = "https://example.com/privacy"
logo = Icons.Default.Lock
@@ -649,6 +678,54 @@ fun AuthenticationScreen() {
}
```
+**FirebaseAuthScreen Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `configuration` | `AuthUIConfiguration` | *Required* | Authentication configuration (providers, theme, etc.) |
+| `onSignInSuccess` | `(AuthResult) -> Unit` | *Required* | Callback when sign-in succeeds |
+| `onSignInFailure` | `(AuthException) -> Unit` | *Required* | Callback when sign-in fails |
+| `onSignInCancelled` | `() -> Unit` | *Required* | Callback when user cancels authentication |
+| `modifier` | `Modifier` | `Modifier` | Modifier for the composable |
+| `authUI` | `FirebaseAuthUI` | `FirebaseAuthUI.getInstance()` | Custom FirebaseAuthUI instance (for multi-app support) |
+| `emailLink` | `String?` | `null` | Email link for passwordless sign-in (see [Email Link Sign-In](#email-link-sign-in)) |
+| `mfaConfiguration` | `MfaConfiguration` | `MfaConfiguration()` | MFA settings (see [Multi-Factor Authentication](#multi-factor-authentication)) |
+| `authenticatedContent` | `@Composable ((AuthState, AuthSuccessUiContext) -> Unit)?` | `null` | Optional content to show after successful authentication |
+
+**Using authenticatedContent:**
+
+Show custom UI after authentication completes, before navigating away:
+
+```kotlin
+FirebaseAuthScreen(
+ configuration = configuration,
+ onSignInSuccess = { result ->
+ // Called after authenticatedContent is dismissed
+ navigateToHome()
+ },
+ onSignInFailure = { exception ->
+ showError(exception)
+ },
+ onSignInCancelled = {
+ finish()
+ },
+ authenticatedContent = { state, uiContext ->
+ // Show a welcome screen or profile completion UI
+ Column(
+ modifier = Modifier.fillMaxSize().padding(24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text("Welcome, ${(state as? AuthState.Success)?.user?.displayName}!")
+ Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = { uiContext.onContinue() }) {
+ Text("Continue to App")
+ }
+ }
+ }
+)
+```
+
### Low-Level API (Advanced)
For maximum control, use the `AuthFlowController`:
@@ -662,7 +739,10 @@ class AuthActivity : ComponentActivity() {
val authUI = FirebaseAuthUI.getInstance()
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email(), AuthProvider.Google())
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
}
controller = authUI.createAuthFlow(configuration)
@@ -860,7 +940,9 @@ val mfaConfig = MfaConfiguration(
)
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email())
+ providers {
+ provider(AuthProvider.Email())
+ }
isMfaEnabled = true
}
```
@@ -970,17 +1052,179 @@ fun ManualMfaChallenge(resolver: MultiFactorResolver) {
## Theming & Customization
-### Material Theme Integration
+FirebaseUI Auth provides flexible theming options to match your app's design:
+
+- **`AuthUITheme.Default`** / **`AuthUITheme.DefaultDark`** / **`AuthUITheme.Adaptive`** - Pre-configured Material Design 3 themes
+- **`.copy()`** - Customize specific properties of the default themes (data class)
+- **`fromMaterialTheme()`** - Inherit from your app's existing Material Theme
+- **Custom theme** - Full control over colors, typography, shapes, and provider button styles
+
+### Using Default Themes
+
+FirebaseUI provides pre-configured themes for light and dark modes:
+
+```kotlin
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
+ theme = AuthUITheme.Default // Light theme
+ // or
+ theme = AuthUITheme.DefaultDark // Dark theme
+}
+```
+
+### Using Adaptive Theme (Recommended)
-FirebaseUI automatically inherits your app's Material Theme:
+`AuthUITheme.Adaptive` automatically switches between light and dark themes based on the system setting:
+
+```kotlin
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
+ theme = AuthUITheme.Adaptive // Adapts to system dark mode
+}
+```
+
+This is the recommended approach for most apps as it provides a seamless experience that respects the user's system preferences.
+
+**Note:** `Adaptive` is a `@Composable` property that evaluates to `Default` (light) or `DefaultDark` (dark) based on `isSystemInDarkTheme()` at composition time.
+
+### Customizing Default Theme
+
+Use `.copy()` to customize specific properties of the default theme:
+
+```kotlin
+@Composable
+fun AuthScreen() {
+ val customTheme = AuthUITheme.Adaptive.copy(
+ providerButtonShape = MaterialTheme.shapes.extraLarge // Pill-shaped buttons
+ )
+
+ val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Email())
+ }
+ theme = customTheme
+ }
+
+ FirebaseAuthScreen(
+ configuration = configuration,
+ onSignInSuccess = { /* ... */ },
+ onSignInFailure = { /* ... */ },
+ onSignInCancelled = { /* ... */ }
+ )
+}
+```
+
+### Theme Behavior & Patterns
+
+FirebaseUI Auth supports two theming patterns with clear precedence rules:
+
+#### Pattern 1: Theme in Configuration Only (Recommended)
+
+The simplest approach is to set the theme only in `authUIConfiguration`:
+
+```kotlin
+val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(AuthProvider.Email())
+ }
+ theme = AuthUITheme.Adaptive // Set theme here
+}
+
+FirebaseAuthScreen(
+ configuration = configuration,
+ onSignInSuccess = { /* ... */ }
+)
+```
+
+**When to use:** This is the recommended pattern for most use cases. It's simple and explicit.
+
+#### Pattern 2: Theme in Wrapper (Optional)
+
+You can also wrap `FirebaseAuthScreen` with `AuthUITheme`:
+
+```kotlin
+val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(AuthProvider.Email())
+ }
+ theme = AuthUITheme.Adaptive // Theme in configuration
+}
+
+AuthUITheme(theme = AuthUITheme.Adaptive) { // Optional wrapper
+ Surface(color = MaterialTheme.colorScheme.background) {
+ FirebaseAuthScreen(
+ configuration = configuration,
+ onSignInSuccess = { /* ... */ }
+ )
+ }
+}
+```
+
+**When to use:** Use this pattern when you have UI elements **outside** of `FirebaseAuthScreen` that need to share the same theme.
+
+#### Theme Precedence Rules
+
+Understanding which theme applies is important:
+
+1. **Configuration theme takes precedence:**
+ ```kotlin
+ val configuration = authUIConfiguration {
+ theme = AuthUITheme.Default // LIGHT theme
+ }
+
+ AuthUITheme(theme = AuthUITheme.DefaultDark) { // DARK wrapper
+ FirebaseAuthScreen(configuration, ...)
+ }
+ // Result: FirebaseAuthScreen uses LIGHT theme (from configuration)
+ ```
+
+2. **Wrapper as fallback:**
+ ```kotlin
+ val configuration = authUIConfiguration {
+ // theme not specified (null)
+ }
+
+ AuthUITheme(theme = AuthUITheme.DefaultDark) { // DARK wrapper
+ FirebaseAuthScreen(configuration, ...)
+ }
+ // Result: FirebaseAuthScreen inherits DARK theme from wrapper
+ ```
+
+3. **Ultimate fallback:**
+ ```kotlin
+ val configuration = authUIConfiguration {
+ // theme not specified (null)
+ }
+
+ FirebaseAuthScreen(configuration, ...) // No wrapper
+ // Result: Uses AuthUITheme.Default (light theme)
+ ```
+
+**Best Practice:** For clarity and consistency, always set `theme` in `authUIConfiguration`. Use the wrapper only if you have additional UI outside `FirebaseAuthScreen`.
+
+### Inheriting from Material Theme
+
+Use `fromMaterialTheme()` to automatically inherit your app's Material Design theme:
```kotlin
@Composable
fun App() {
MyAppTheme { // Your existing Material3 theme
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email())
- theme = AuthUITheme.fromMaterialTheme() // Inherits MyAppTheme
+ providers {
+ provider(AuthProvider.Email())
+ }
+ theme = AuthUITheme.fromMaterialTheme() // Inherits colors, typography, shapes
}
FirebaseAuthScreen(
@@ -991,9 +1235,23 @@ fun App() {
}
```
-### Custom Theme
+You can also customize while inheriting:
+
+```kotlin
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ }
+ theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(16.dp) // Override button shape
+ )
+}
+```
+
+### Creating a Completely Custom Theme
-Create a completely custom theme:
+Build a theme from scratch with full control:
```kotlin
val customTheme = AuthUITheme(
@@ -1011,46 +1269,244 @@ val customTheme = AuthUITheme(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
- )
+ ),
+ providerButtonShape = RoundedCornerShape(12.dp)
)
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email())
+ providers {
+ provider(AuthProvider.Email())
+ }
theme = customTheme
}
```
### Provider Button Styling
-Customize individual provider button styling:
+#### Setting shapes for all provider buttons
+
+**Option 1: Using `.copy()` on default theme:**
+
+```kotlin
+val customTheme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp) // Applies to all provider buttons
+)
+
+val configuration = authUIConfiguration {
+ providers = listOf(AuthProvider.Google(), AuthProvider.Facebook(), AuthProvider.Email())
+ theme = customTheme
+}
+```
+
+**Option 2: Using `fromMaterialTheme()`:**
+
+```kotlin
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ }
+ theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(16.dp)
+ )
+}
+```
+
+**Option 3: Creating custom theme:**
+
+```kotlin
+val customTheme = AuthUITheme(
+ colorScheme = MaterialTheme.colorScheme,
+ typography = MaterialTheme.typography,
+ shapes = MaterialTheme.shapes,
+ providerButtonShape = RoundedCornerShape(12.dp)
+)
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ provider(AuthProvider.Email())
+ }
+ theme = customTheme
+}
+```
+
+#### Customizing individual provider buttons
+
+Customize specific provider buttons using the pre-defined `ProviderStyleDefaults` constants:
+
+**Using `.copy()` with default theme:**
```kotlin
val customProviderStyles = mapOf(
- "google.com" to AuthUITheme.ProviderStyle(
- backgroundColor = Color.White,
- contentColor = Color(0xFF757575),
- iconTint = null, // Use original colors
+ "google.com" to ProviderStyleDefaults.Google.copy(
shape = RoundedCornerShape(8.dp),
elevation = 4.dp
),
- "facebook.com" to AuthUITheme.ProviderStyle(
- backgroundColor = Color(0xFF1877F2),
- contentColor = Color.White,
- shape = RoundedCornerShape(12.dp),
+ "facebook.com" to ProviderStyleDefaults.Facebook.copy(
+ shape = RoundedCornerShape(24.dp),
elevation = 0.dp
)
)
val customTheme = AuthUITheme.Default.copy(
- providerStyles = customProviderStyles
+ providerButtonShape = RoundedCornerShape(12.dp), // Default for all
+ providerStyles = customProviderStyles // Specific overrides
)
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Google(), AuthProvider.Facebook())
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ }
theme = customTheme
}
```
+**Using `fromMaterialTheme()`:**
+
+```kotlin
+val customProviderStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = RoundedCornerShape(8.dp),
+ elevation = 4.dp
+ )
+)
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Google())
+ provider(AuthProvider.Facebook())
+ }
+ theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(12.dp),
+ providerStyles = customProviderStyles
+ )
+}
+```
+
+#### Complete customization example
+
+Real-world example combining global and per-provider customizations:
+
+```kotlin
+// Define custom styles for specific providers
+val customProviderStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = RoundedCornerShape(24.dp), // Pill-shaped Google button
+ elevation = 6.dp
+ ),
+ "facebook.com" to ProviderStyleDefaults.Facebook.copy(
+ shape = RoundedCornerShape(8.dp), // Medium rounded Facebook button
+ elevation = 0.dp // Flat design
+ )
+ // Email provider will use the global providerButtonShape
+)
+
+// Customize default theme with global button shape and per-provider styles
+val customTheme = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp), // Global default for all buttons
+ providerStyles = customProviderStyles // Specific overrides
+)
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Google()) // Uses custom shape (24.dp)
+ provider(AuthProvider.Facebook()) // Uses custom shape (8.dp)
+ provider(AuthProvider.Email()) // Uses global shape (12.dp)
+ }
+ theme = customTheme
+}
+```
+
+### Screen Transitions
+
+Customize the animations when navigating between screens using the `AuthUITransitions` object:
+
+**Slide animations:**
+
+```kotlin
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import com.firebase.ui.auth.configuration.AuthUITransitions
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
+ transitions = AuthUITransitions(
+ enterTransition = { slideInHorizontally { it } }, // Slide in from right
+ exitTransition = { slideOutHorizontally { -it } }, // Slide out to left
+ popEnterTransition = { slideInHorizontally { -it } }, // Slide in from left
+ popExitTransition = { slideOutHorizontally { it } } // Slide out to right
+ )
+}
+```
+
+**Fade animations (default):**
+
+```kotlin
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import com.firebase.ui.auth.configuration.AuthUITransitions
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Phone())
+ }
+ transitions = AuthUITransitions(
+ enterTransition = { fadeIn() },
+ exitTransition = { fadeOut() },
+ popEnterTransition = { fadeIn() },
+ popExitTransition = { fadeOut() }
+ )
+}
+```
+
+**Scale animations:**
+
+```kotlin
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import com.firebase.ui.auth.configuration.AuthUITransitions
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Facebook())
+ }
+ transitions = AuthUITransitions(
+ enterTransition = { fadeIn() + scaleIn(initialScale = 0.9f) },
+ exitTransition = { fadeOut() + scaleOut(targetScale = 0.9f) },
+ popEnterTransition = { fadeIn() + scaleIn(initialScale = 0.9f) },
+ popExitTransition = { fadeOut() + scaleOut(targetScale = 0.9f) }
+ )
+}
+```
+
+**Vertical slide:**
+
+```kotlin
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import com.firebase.ui.auth.configuration.AuthUITransitions
+
+val configuration = authUIConfiguration {
+ providers {
+ provider(AuthProvider.Email())
+ }
+ transitions = AuthUITransitions(
+ enterTransition = { slideInVertically { it } }, // Slide up
+ exitTransition = { slideOutVertically { -it } } // Slide down
+ )
+}
+```
+
+> **Note:** If not specified, default fade in/out transitions with 700ms duration are used.
+
## Advanced Features
### Anonymous User Upgrade
@@ -1060,11 +1516,11 @@ Seamlessly upgrade anonymous users to permanent accounts:
```kotlin
// 1. Configure anonymous authentication with upgrade enabled
val configuration = authUIConfiguration {
- providers = listOf(
- AuthProvider.Anonymous(),
- AuthProvider.Email(),
- AuthProvider.Google()
- )
+ providers {
+ provider(AuthProvider.Anonymous())
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
isAnonymousUpgradeEnabled = true
}
@@ -1095,7 +1551,9 @@ val emailProvider = AuthProvider.Email(
)
val configuration = authUIConfiguration {
- providers = listOf(emailProvider)
+ providers {
+ provider(emailProvider)
+ }
}
```
@@ -1221,7 +1679,9 @@ Credential Manager is enabled by default. To disable:
```kotlin
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email())
+ providers {
+ provider(AuthProvider.Email())
+ }
isCredentialManagerEnabled = false
}
```
@@ -1293,7 +1753,9 @@ class SpanishStringProvider(context: Context) : AuthUIStringProvider {
}
val configuration = authUIConfiguration {
- providers = listOf(AuthProvider.Email())
+ providers {
+ provider(AuthProvider.Email())
+ }
stringProvider = SpanishStringProvider(context)
locale = Locale("es", "ES")
}
@@ -1414,10 +1876,10 @@ signInLauncher.launch(signInIntent);
```kotlin
// New approach with Composable
val configuration = authUIConfiguration {
- providers = listOf(
- AuthProvider.Email(),
- AuthProvider.Google()
- )
+ providers {
+ provider(AuthProvider.Email())
+ provider(AuthProvider.Google())
+ }
theme = AuthUITheme.fromMaterialTheme()
}
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index 14a0a7f98..f7f16d715 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -104,7 +104,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
- implementation("androidx.navigation:navigation-compose:2.8.3")
+ api("androidx.navigation:navigation-compose:2.8.3")
implementation("com.google.zxing:core:3.5.3")
annotationProcessor(Config.Libs.Androidx.lifecycleCompiler)
diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthActivity.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthActivity.kt
index af71920d7..168670da1 100644
--- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthActivity.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthActivity.kt
@@ -130,7 +130,7 @@ class FirebaseAuthActivity : ComponentActivity() {
// Set up Compose UI
setContent {
- AuthUITheme(theme = configuration.theme) {
+ AuthUITheme {
FirebaseAuthScreen(
authUI = authUI,
configuration = configuration,
diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt
index df5a49173..9f829a37f 100644
--- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt
@@ -19,13 +19,14 @@ import android.content.Intent
import androidx.annotation.RestrictTo
import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
+import com.firebase.ui.auth.configuration.auth_provider.signOutFromFacebook
import com.firebase.ui.auth.configuration.auth_provider.signOutFromGoogle
+import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuth.AuthStateListener
import com.google.firebase.auth.FirebaseUser
-import com.google.firebase.auth.ktx.auth
-import com.google.firebase.ktx.Firebase
+import com.google.firebase.auth.auth
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -77,6 +78,9 @@ class FirebaseAuthUI private constructor(
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
var testCredentialManagerProvider: AuthProvider.Google.CredentialManagerProvider? = null
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ var testLoginManagerProvider: AuthProvider.Facebook.LoginManagerProvider? = null
+
/**
* Checks whether a user is currently signed in.
*
@@ -367,6 +371,7 @@ class FirebaseAuthUI private constructor(
auth.signOut()
.also {
signOutFromGoogle(context)
+ signOutFromFacebook()
}
// Update state to idle (user signed out)
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUIConfiguration.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUIConfiguration.kt
index 68087b419..3fa7f394b 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUIConfiguration.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUIConfiguration.kt
@@ -36,7 +36,7 @@ annotation class AuthUIConfigurationDsl
class AuthUIConfigurationBuilder {
var context: Context? = null
private val providers = mutableListOf()
- var theme: AuthUITheme = AuthUITheme.Default
+ var theme: AuthUITheme? = null
var locale: Locale? = null
var stringProvider: AuthUIStringProvider? = null
var isCredentialManagerEnabled: Boolean = true
@@ -49,6 +49,7 @@ class AuthUIConfigurationBuilder {
var isNewEmailAccountsAllowed: Boolean = true
var isDisplayNameRequired: Boolean = true
var isProviderChoiceAlwaysShown: Boolean = false
+ var transitions: AuthUITransitions? = null
fun providers(block: AuthProvidersBuilder.() -> Unit) =
providers.addAll(AuthProvidersBuilder().apply(block).build())
@@ -112,7 +113,8 @@ class AuthUIConfigurationBuilder {
passwordResetActionCodeSettings = passwordResetActionCodeSettings,
isNewEmailAccountsAllowed = isNewEmailAccountsAllowed,
isDisplayNameRequired = isDisplayNameRequired,
- isProviderChoiceAlwaysShown = isProviderChoiceAlwaysShown
+ isProviderChoiceAlwaysShown = isProviderChoiceAlwaysShown,
+ transitions = transitions
)
}
}
@@ -132,9 +134,10 @@ class AuthUIConfiguration(
val providers: List = emptyList(),
/**
- * The theming configuration for the UI. Default to [AuthUITheme.Default].
+ * The theming configuration for the UI. If null, inherits from the outer AuthUITheme wrapper
+ * or defaults to [AuthUITheme.Default] if no wrapper is present.
*/
- val theme: AuthUITheme = AuthUITheme.Default,
+ val theme: AuthUITheme? = null,
/**
* The locale for internationalization.
@@ -195,4 +198,10 @@ class AuthUIConfiguration(
* Always shows the provider selection screen, even if only one is enabled.
*/
val isProviderChoiceAlwaysShown: Boolean = false,
+
+ /**
+ * Custom screen transition animations.
+ * If null, uses default fade in/out transitions.
+ */
+ val transitions: AuthUITransitions? = null,
)
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUITransitions.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUITransitions.kt
new file mode 100644
index 000000000..b37dc34e1
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/AuthUITransitions.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.configuration
+
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.navigation.NavBackStackEntry
+
+/**
+ * Container for screen transition animations used in Firebase Auth UI.
+ *
+ * @property enterTransition Transition when entering a new screen
+ * @property exitTransition Transition when exiting current screen
+ * @property popEnterTransition Transition when returning to previous screen (back navigation)
+ * @property popExitTransition Transition when exiting during back navigation
+ */
+data class AuthUITransitions(
+ val enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition)? = null,
+ val exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition)? = null,
+ val popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition)? = null,
+ val popExitTransition: (AnimatedContentTransitionScope.() -> ExitTransition)? = null,
+)
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
index fb8e55775..1b659a8fc 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AuthProvider.kt
@@ -21,6 +21,7 @@ import android.util.Log
import androidx.annotation.RestrictTo
import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri
+import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.datastore.preferences.core.stringPreferencesKey
@@ -568,6 +569,11 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
filterByAuthorizedAccounts: Boolean,
autoSelectEnabled: Boolean
): GoogleSignInResult
+
+ suspend fun clearCredentialState(
+ context: Context,
+ credentialManager: CredentialManager,
+ )
}
/**
@@ -604,6 +610,13 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
photoUrl = googleIdTokenCredential.profilePictureUri,
)
}
+
+ override suspend fun clearCredentialState(
+ context: Context,
+ credentialManager: CredentialManager,
+ ) {
+ credentialManager.clearCredentialState(ClearCredentialStateRequest())
+ }
}
}
@@ -655,21 +668,28 @@ abstract class AuthProvider(open val providerId: String, open val providerName:
}
/**
- * An interface to wrap the static `FacebookAuthProvider.getCredential` method to make it testable.
+ * An interface to wrap Facebook LoginManager and credential operations to make them testable.
* @suppress
*/
- internal interface CredentialProvider {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ interface LoginManagerProvider {
fun getCredential(token: String): AuthCredential
+ fun logOut()
}
/**
- * The default implementation of [CredentialProvider] that calls the static method.
+ * The default implementation of [LoginManagerProvider].
* @suppress
*/
- internal class DefaultCredentialProvider : CredentialProvider {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ class DefaultLoginManagerProvider : LoginManagerProvider {
override fun getCredential(token: String): AuthCredential {
return FacebookAuthProvider.getCredential(token)
}
+
+ override fun logOut() {
+ com.facebook.login.LoginManager.getInstance().logOut()
+ }
}
/**
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt
index 878fd03a5..8d4bae6d1 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt
@@ -16,6 +16,7 @@ package com.firebase.ui.auth.configuration.auth_provider
import android.content.Context
import android.net.Uri
+import android.util.Log
import com.firebase.ui.auth.R
import com.firebase.ui.auth.AuthException
import com.firebase.ui.auth.AuthState
@@ -23,10 +24,14 @@ import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider.Companion.canUpgradeAnonymous
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider.Companion.mergeProfile
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialCancelledException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialHandler
import com.firebase.ui.auth.util.EmailLinkPersistenceManager
import com.firebase.ui.auth.util.EmailLinkParser
import com.firebase.ui.auth.util.PersistenceManager
import com.firebase.ui.auth.util.SessionUtils
+import com.firebase.ui.auth.util.SignInPreferenceManager
import com.google.firebase.FirebaseApp
import com.google.firebase.auth.ActionCodeSettings
import com.google.firebase.auth.AuthCredential
@@ -38,6 +43,7 @@ import com.google.firebase.auth.FirebaseAuthUserCollisionException
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.tasks.await
+private const val TAG = "EmailAuthProvider"
/**
* Creates an email/password account or links the credential to an anonymous user.
@@ -160,6 +166,37 @@ internal suspend fun FirebaseAuthUI.createOrLinkUserWithEmailAndPassword(
mergeProfile(auth, name, null)
}
}
+
+ // Save credentials to Credential Manager if enabled
+ if (config.isCredentialManagerEnabled) {
+ try {
+ val credentialHandler = PasswordCredentialHandler(context)
+ credentialHandler.savePassword(email, password)
+ Log.d(TAG, "Password credential saved successfully for: $email")
+ } catch (e: PasswordCredentialCancelledException) {
+ // User cancelled - this is fine, don't break the auth flow
+ Log.d(TAG, "User cancelled credential save for: $email")
+ } catch (e: PasswordCredentialException) {
+ // Failed to save - log but don't break the auth flow
+ Log.w(TAG, "Failed to save password credential for: $email", e)
+ }
+ }
+
+ // Save sign-in preference for "Continue as..." feature
+ if (result != null) {
+ try {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = "password",
+ identifier = email
+ )
+ Log.d(TAG, "Sign-in preference saved for: $email")
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ Log.w(TAG, "Failed to save sign-in preference for: $email", e)
+ }
+ }
+
updateAuthState(AuthState.Idle)
return result
} catch (e: FirebaseAuthUserCollisionException) {
@@ -281,6 +318,7 @@ internal suspend fun FirebaseAuthUI.signInWithEmailAndPassword(
email: String,
password: String,
credentialForLinking: AuthCredential? = null,
+ skipCredentialSave: Boolean = false,
): AuthResult? {
try {
updateAuthState(AuthState.Loading("Signing in..."))
@@ -361,7 +399,38 @@ internal suspend fun FirebaseAuthUI.signInWithEmailAndPassword(
result
}
}
- }.also {
+ }.also { result ->
+ // Save credentials to Credential Manager if enabled
+ // Skip if user signed in with a retrieved credential (already saved)
+ if (config.isCredentialManagerEnabled && result != null && !skipCredentialSave) {
+ try {
+ val credentialHandler = PasswordCredentialHandler(context)
+ credentialHandler.savePassword(email, password)
+ Log.d(TAG, "Password credential saved successfully for: $email")
+ } catch (e: PasswordCredentialCancelledException) {
+ // User cancelled - this is fine, don't break the auth flow
+ Log.d(TAG, "User cancelled credential save for: $email")
+ } catch (e: PasswordCredentialException) {
+ // Failed to save - log but don't break the auth flow
+ Log.w(TAG, "Failed to save password credential for: $email", e)
+ }
+ }
+
+ // Save sign-in preference for "Continue as..." feature
+ if (result != null) {
+ try {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = "password",
+ identifier = email
+ )
+ Log.d(TAG, "Sign-in preference saved for: $email")
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ Log.w(TAG, "Failed to save sign-in preference for: $email", e)
+ }
+ }
+
updateAuthState(AuthState.Idle)
}
} catch (e: FirebaseAuthMultiFactorException) {
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt
index 9939a4b47..28ef45636 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt
@@ -32,6 +32,7 @@ import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.util.EmailLinkPersistenceManager
+import com.firebase.ui.auth.util.SignInPreferenceManager
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
@@ -143,7 +144,7 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook(
config: AuthUIConfiguration,
provider: AuthProvider.Facebook,
accessToken: AccessToken,
- credentialProvider: AuthProvider.Facebook.CredentialProvider = AuthProvider.Facebook.DefaultCredentialProvider(),
+ credentialProvider: AuthProvider.Facebook.LoginManagerProvider = AuthProvider.Facebook.DefaultLoginManagerProvider(),
) {
try {
updateAuthState(
@@ -158,6 +159,23 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook(
displayName = profileData?.displayName,
photoUrl = profileData?.photoUrl,
)
+
+ // Save sign-in preference for "Continue as..." feature
+ try {
+ val user = auth.currentUser
+ val identifier = user?.email
+ if (identifier != null) {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = provider.providerId,
+ identifier = identifier
+ )
+ android.util.Log.d("FacebookAuthProvider", "Sign-in preference saved for: $identifier")
+ }
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ android.util.Log.w("FacebookAuthProvider", "Failed to save sign-in preference", e)
+ }
} catch (e: AuthException.AccountLinkingRequiredException) {
// Account collision occurred - save Facebook credential for linking after email link sign-in
// This happens when a user tries to sign in with Facebook but an email link account exists
@@ -192,3 +210,23 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook(
}
}
+/**
+ * Signs out the current user from Facebook.
+ *
+ * Invokes Facebook's LoginManager to log out the user from their Facebook session.
+ * This method silently catches and ignores any exceptions that may occur during the
+ * logout process to ensure the sign-out flow continues even if Facebook logout fails.
+ *
+ * This is typically called as part of the overall sign-out flow when a user signs out
+ * from Firebase Authentication.
+ */
+internal fun FirebaseAuthUI.signOutFromFacebook(
+ loginManagerProvider: AuthProvider.Facebook.LoginManagerProvider = AuthProvider.Facebook.DefaultLoginManagerProvider(),
+) {
+ try {
+ if (Provider.fromId(getCurrentUser()?.providerId) != Provider.FACEBOOK) return
+ (testLoginManagerProvider ?: loginManagerProvider).logOut()
+ } catch (e: Exception) {
+ Log.e("FacebookAuthProvider", "Error during Facebook sign out", e)
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
index d42362692..2afdb3599 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt
@@ -1,10 +1,10 @@
package com.firebase.ui.auth.configuration.auth_provider
import android.content.Context
+import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.exceptions.NoCredentialException
@@ -13,6 +13,7 @@ import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.util.EmailLinkPersistenceManager
+import com.firebase.ui.auth.util.SignInPreferenceManager
import com.google.android.gms.common.api.Scope
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import kotlinx.coroutines.CancellationException
@@ -149,6 +150,23 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle(
displayName = result.displayName,
photoUrl = result.photoUrl,
)
+
+ // Save sign-in preference for "Continue as..." feature
+ try {
+ val user = auth.currentUser
+ val identifier = user?.email
+ if (identifier != null) {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = provider.providerId,
+ identifier = identifier
+ )
+ Log.d("GoogleAuthProvider", "Sign-in preference saved for: $identifier")
+ }
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ android.util.Log.w("GoogleAuthProvider", "Failed to save sign-in preference", e)
+ }
} catch (e: AuthException.AccountLinkingRequiredException) {
// Account collision occurred - save Facebook credential for linking after email link sign-in
// This happens when a user tries to sign in with Facebook but an email link account exists
@@ -198,13 +216,17 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle(
*
* @param context Android context for Credential Manager
*/
-internal suspend fun signOutFromGoogle(context: Context) {
+internal suspend fun FirebaseAuthUI.signOutFromGoogle(
+ context: Context,
+ credentialManagerProvider: AuthProvider.Google.CredentialManagerProvider = AuthProvider.Google.DefaultCredentialManagerProvider(),
+) {
try {
- val credentialManager = CredentialManager.create(context)
- credentialManager.clearCredentialState(
- ClearCredentialStateRequest()
+ if (Provider.fromId(getCurrentUser()?.providerId) != Provider.GOOGLE) return
+ (testCredentialManagerProvider ?: credentialManagerProvider).clearCredentialState(
+ context = context,
+ credentialManager = CredentialManager.create(context)
)
- } catch (_: Exception) {
-
+ } catch (e: Exception) {
+ Log.e("GoogleAuthProvider", "Error during Google sign out", e)
}
}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt
index 615aa6982..485065746 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt
@@ -1,6 +1,7 @@
package com.firebase.ui.auth.configuration.auth_provider
import android.app.Activity
+import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -9,6 +10,7 @@ import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider.Companion.canUpgradeAnonymous
+import com.firebase.ui.auth.util.SignInPreferenceManager
import com.google.firebase.auth.FirebaseAuthUserCollisionException
import com.google.firebase.auth.OAuthCredential
import com.google.firebase.auth.OAuthProvider
@@ -48,6 +50,7 @@ import kotlinx.coroutines.tasks.await
*/
@Composable
internal fun FirebaseAuthUI.rememberOAuthSignInHandler(
+ context: Context,
activity: Activity?,
config: AuthUIConfiguration,
provider: AuthProvider.OAuth,
@@ -63,6 +66,7 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler(
coroutineScope.launch {
try {
signInWithProvider(
+ context = context,
config = config,
activity = activity,
provider = provider
@@ -119,6 +123,7 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler(
* @see signInAndLinkWithCredential
*/
internal suspend fun FirebaseAuthUI.signInWithProvider(
+ context: Context,
config: AuthUIConfiguration,
activity: Activity,
provider: AuthProvider.OAuth,
@@ -172,6 +177,24 @@ internal suspend fun FirebaseAuthUI.signInWithProvider(
val credential = authResult?.credential as? OAuthCredential
if (credential != null) {
// The user is already signed in via startActivityForSignInWithProvider/startActivityForLinkWithProvider
+
+ // Save sign-in preference for "Continue as..." feature
+ try {
+ val user = auth.currentUser
+ val identifier = user?.email
+ if (identifier != null) {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = provider.providerId,
+ identifier = identifier
+ )
+ android.util.Log.d("OAuthProvider", "Sign-in preference saved for: $identifier (${provider.providerId})")
+ }
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ android.util.Log.w("OAuthProvider", "Failed to save sign-in preference", e)
+ }
+
// Just update state to Idle
updateAuthState(AuthState.Idle)
} else {
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt
index 95dbdcf78..0be8ee8fa 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt
@@ -1,10 +1,12 @@
package com.firebase.ui.auth.configuration.auth_provider
import android.app.Activity
+import android.content.Context
import com.firebase.ui.auth.AuthException
import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
+import com.firebase.ui.auth.util.SignInPreferenceManager
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.MultiFactorSession
import com.google.firebase.auth.PhoneAuthCredential
@@ -197,6 +199,7 @@ internal suspend fun FirebaseAuthUI.verifyPhoneNumber(
* @throws AuthException.NetworkException if a network error occurs
*/
internal suspend fun FirebaseAuthUI.submitVerificationCode(
+ context: Context,
config: AuthUIConfiguration,
verificationId: String,
code: String,
@@ -206,6 +209,7 @@ internal suspend fun FirebaseAuthUI.submitVerificationCode(
updateAuthState(AuthState.Loading("Submitting verification code..."))
val credential = credentialProvider.getCredential(verificationId, code)
return signInWithPhoneAuthCredential(
+ context = context,
config = config,
credential = credential
)
@@ -288,15 +292,37 @@ internal suspend fun FirebaseAuthUI.submitVerificationCode(
* @throws AuthException.NetworkException if a network error occurs
*/
internal suspend fun FirebaseAuthUI.signInWithPhoneAuthCredential(
+ context: Context,
config: AuthUIConfiguration,
credential: PhoneAuthCredential,
): AuthResult? {
try {
updateAuthState(AuthState.Loading("Signing in with phone..."))
- return signInAndLinkWithCredential(
+ val result = signInAndLinkWithCredential(
config = config,
credential = credential,
)
+
+ // Save sign-in preference for "Continue as..." feature
+ if (result != null) {
+ try {
+ val user = auth.currentUser
+ val identifier = user?.phoneNumber
+ if (identifier != null) {
+ SignInPreferenceManager.saveLastSignIn(
+ context = context,
+ providerId = "phone",
+ identifier = identifier
+ )
+ android.util.Log.d("PhoneAuthProvider", "Sign-in preference saved for: $identifier")
+ }
+ } catch (e: Exception) {
+ // Failed to save preference - log but don't break auth flow
+ android.util.Log.w("PhoneAuthProvider", "Failed to save sign-in preference", e)
+ }
+ }
+
+ return result
} catch (e: CancellationException) {
val cancelledException = AuthException.AuthCancelledException(
message = "Sign in with phone was cancelled",
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt
index f00cffee6..df71966ab 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt
@@ -97,6 +97,33 @@ interface AuthUIStringProvider {
/** Button text for Yahoo sign-in option */
val signInWithYahoo: String
+ /** Button text for Google continue option */
+ val continueWithGoogle: String
+
+ /** Button text for Facebook continue option */
+ val continueWithFacebook: String
+
+ /** Button text for Twitter continue option */
+ val continueWithTwitter: String
+
+ /** Button text for Github continue option */
+ val continueWithGithub: String
+
+ /** Button text for Email continue option */
+ val continueWithEmail: String
+
+ /** Button text for Phone continue option */
+ val continueWithPhone: String
+
+ /** Button text for Apple continue option */
+ val continueWithApple: String
+
+ /** Button text for Microsoft continue option */
+ val continueWithMicrosoft: String
+
+ /** Button text for Yahoo continue option */
+ val continueWithYahoo: String
+
/** Error message when email address field is empty */
val missingEmailAddress: String
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt
index f6c3f03ad..a0e53e2a9 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt
@@ -80,6 +80,28 @@ class DefaultAuthUIStringProvider(
override val signInWithYahoo: String
get() = localizedContext.getString(R.string.fui_sign_in_with_yahoo)
+ /**
+ * Auth Provider "Continue With" Button Strings
+ */
+ override val continueWithGoogle: String
+ get() = localizedContext.getString(R.string.fui_continue_with_google)
+ override val continueWithFacebook: String
+ get() = localizedContext.getString(R.string.fui_continue_with_facebook)
+ override val continueWithTwitter: String
+ get() = localizedContext.getString(R.string.fui_continue_with_twitter)
+ override val continueWithGithub: String
+ get() = localizedContext.getString(R.string.fui_continue_with_github)
+ override val continueWithEmail: String
+ get() = localizedContext.getString(R.string.fui_continue_with_email)
+ override val continueWithPhone: String
+ get() = localizedContext.getString(R.string.fui_continue_with_phone)
+ override val continueWithApple: String
+ get() = localizedContext.getString(R.string.fui_continue_with_apple)
+ override val continueWithMicrosoft: String
+ get() = localizedContext.getString(R.string.fui_continue_with_microsoft)
+ override val continueWithYahoo: String
+ get() = localizedContext.getString(R.string.fui_continue_with_yahoo)
+
/**
* Email Validator Strings
*/
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt
index a2e8e143a..79e78f80f 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt
@@ -15,7 +15,6 @@
package com.firebase.ui.auth.configuration.theme
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@@ -25,11 +24,19 @@ import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+/**
+ * CompositionLocal providing access to the current AuthUITheme.
+ * This allows components to access theme configuration including provider styles and shapes.
+ */
+val LocalAuthUITheme = staticCompositionLocalOf { AuthUITheme.Default }
+
/**
* Theming configuration for the entire Auth UI.
*/
@@ -45,21 +52,95 @@ class AuthUITheme(
val typography: Typography,
/**
- * The shapes to use for UI elements.
+ * The shapes to use for UI elements (text fields, cards, etc.).
*/
val shapes: Shapes,
/**
- * A map of provider IDs to custom styling.
+ * A map of provider IDs to custom styling. Use this to customize individual
+ * provider buttons with specific colors, icons, shapes, and elevation.
+ *
+ * Example:
+ * ```kotlin
+ * providerStyles = mapOf(
+ * "google.com" to ProviderStyleDefaults.Google.copy(
+ * shape = RoundedCornerShape(12.dp)
+ * )
+ * )
+ * ```
+ */
+ val providerStyles: Map = emptyMap(),
+
+ /**
+ * Default shape for all provider buttons. If not set, defaults to RoundedCornerShape(4.dp).
+ * Individual provider styles can override this shape.
+ *
+ * Example:
+ * ```kotlin
+ * providerButtonShape = RoundedCornerShape(12.dp)
+ * ```
*/
- val providerStyles: Map = emptyMap()
+ val providerButtonShape: Shape? = null,
) {
+ /**
+ * Creates a copy of this AuthUITheme, optionally overriding specific properties.
+ *
+ * @param colorScheme The color scheme to use. Defaults to this theme's color scheme.
+ * @param typography The typography to use. Defaults to this theme's typography.
+ * @param shapes The shapes to use. Defaults to this theme's shapes.
+ * @param providerStyles Custom styling for individual providers. Defaults to this theme's provider styles.
+ * @param providerButtonShape Default shape for provider buttons. Defaults to this theme's provider button shape.
+ * @return A new AuthUITheme instance with the specified properties.
+ */
+ fun copy(
+ colorScheme: ColorScheme = this.colorScheme,
+ typography: Typography = this.typography,
+ shapes: Shapes = this.shapes,
+ providerStyles: Map = this.providerStyles,
+ providerButtonShape: Shape? = this.providerButtonShape,
+ ): AuthUITheme {
+ return AuthUITheme(
+ colorScheme = colorScheme,
+ typography = typography,
+ shapes = shapes,
+ providerStyles = providerStyles,
+ providerButtonShape = providerButtonShape
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is AuthUITheme) return false
+
+ if (colorScheme != other.colorScheme) return false
+ if (typography != other.typography) return false
+ if (shapes != other.shapes) return false
+ if (providerStyles != other.providerStyles) return false
+ if (providerButtonShape != other.providerButtonShape) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = colorScheme.hashCode()
+ result = 31 * result + typography.hashCode()
+ result = 31 * result + shapes.hashCode()
+ result = 31 * result + providerStyles.hashCode()
+ result = 31 * result + (providerButtonShape?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun toString(): String {
+ return "AuthUITheme(colorScheme=$colorScheme, typography=$typography, shapes=$shapes, " +
+ "providerStyles=$providerStyles, providerButtonShape=$providerButtonShape)"
+ }
+
/**
* A class nested within AuthUITheme that defines the visual appearance of a specific
* provider button, allowing for per-provider branding and customization.
*/
- class ProviderStyle(
+ data class ProviderStyle(
/**
* The provider's icon.
*/
@@ -79,17 +160,18 @@ class AuthUITheme(
* An optional tint color for the provider's icon. If null,
* the icon's intrinsic color is used.
*/
- var iconTint: Color? = null,
+ val iconTint: Color? = null,
/**
- * The shape of the button container. Defaults to RoundedCornerShape(4.dp).
+ * The shape of the button container. If null, uses the theme's providerButtonShape
+ * or falls back to RoundedCornerShape(4.dp).
*/
- val shape: Shape = RoundedCornerShape(4.dp),
+ val shape: Shape? = null,
/**
* The shadow elevation for the button. Defaults to 2.dp.
*/
- val elevation: Dp = 2.dp
+ val elevation: Dp = 2.dp,
) {
internal companion object {
/**
@@ -123,19 +205,26 @@ class AuthUITheme(
providerStyles = ProviderStyleDefaults.default
)
+ val Adaptive: AuthUITheme
+ @Composable get() = if (isSystemInDarkTheme()) DefaultDark else Default
+
/**
- * Creates a theme inheriting the app's current Material
- * Theme settings.
+ * Creates a theme inheriting the app's current Material Theme settings.
+ *
+ * @param providerStyles Custom styling for individual providers. Defaults to standard provider styles.
+ * @param providerButtonShape Default shape for all provider buttons. If null, uses RoundedCornerShape(4.dp).
*/
@Composable
fun fromMaterialTheme(
- providerStyles: Map = ProviderStyleDefaults.default
+ providerStyles: Map = ProviderStyleDefaults.default,
+ providerButtonShape: Shape? = null,
): AuthUITheme {
return AuthUITheme(
colorScheme = MaterialTheme.colorScheme,
typography = MaterialTheme.typography,
shapes = MaterialTheme.shapes,
- providerStyles = providerStyles
+ providerStyles = providerStyles,
+ providerButtonShape = providerButtonShape
)
}
@@ -152,14 +241,17 @@ class AuthUITheme(
@Composable
fun AuthUITheme(
- theme: AuthUITheme = if (isSystemInDarkTheme())
- AuthUITheme.DefaultDark else AuthUITheme.Default,
- content: @Composable () -> Unit
+ theme: AuthUITheme = AuthUITheme.Adaptive,
+ content: @Composable () -> Unit,
) {
- MaterialTheme(
- colorScheme = theme.colorScheme,
- typography = theme.typography,
- shapes = theme.shapes,
- content = content
- )
+ CompositionLocalProvider(
+ LocalAuthUITheme provides theme
+ ) {
+ MaterialTheme(
+ colorScheme = theme.colorScheme,
+ typography = theme.typography,
+ shapes = theme.shapes,
+ content = content
+ )
+ }
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/ProviderStyleDefaults.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/ProviderStyleDefaults.kt
index 051758528..c4721b395 100644
--- a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/ProviderStyleDefaults.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/ProviderStyleDefaults.kt
@@ -27,90 +27,82 @@ import com.firebase.ui.auth.configuration.auth_provider.Provider
*
* The styles are automatically applied when using [AuthUITheme.Default] or can be
* customized by passing a modified map to [AuthUITheme.fromMaterialTheme].
+ *
+ * Individual provider styles can be accessed and customized using the public properties
+ * (e.g., [Google], [Facebook]) and then modified using the [AuthUITheme.ProviderStyle.copy] method.
*/
-internal object ProviderStyleDefaults {
- val default: Map
- get() = Provider.entries.associate { provider ->
- when (provider) {
- Provider.GOOGLE -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_googleg_color_24dp),
- backgroundColor = Color.White,
- contentColor = Color(0xFF757575)
- )
- }
+object ProviderStyleDefaults {
+ val Google = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_googleg_color_24dp),
+ backgroundColor = Color.White,
+ contentColor = Color(0xFF757575)
+ )
- Provider.FACEBOOK -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_facebook_white_22dp),
- backgroundColor = Color(0xFF1877F2),
- contentColor = Color.White
- )
- }
+ val Facebook = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_facebook_white_22dp),
+ backgroundColor = Color(0xFF1877F2),
+ contentColor = Color.White
+ )
- Provider.TWITTER -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_twitter_x_white_24dp),
- backgroundColor = Color.Black,
- contentColor = Color.White
- )
- }
+ val Twitter = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_twitter_x_white_24dp),
+ backgroundColor = Color.Black,
+ contentColor = Color.White
+ )
- Provider.GITHUB -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_github_white_24dp),
- backgroundColor = Color(0xFF24292E),
- contentColor = Color.White
- )
- }
+ val Github = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_github_white_24dp),
+ backgroundColor = Color(0xFF24292E),
+ contentColor = Color.White
+ )
- Provider.EMAIL -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_mail_white_24dp),
- backgroundColor = Color(0xFFD0021B),
- contentColor = Color.White
- )
- }
+ val Email = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_mail_white_24dp),
+ backgroundColor = Color(0xFFD0021B),
+ contentColor = Color.White
+ )
- Provider.PHONE -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_phone_white_24dp),
- backgroundColor = Color(0xFF43C5A5),
- contentColor = Color.White
- )
- }
+ val Phone = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_phone_white_24dp),
+ backgroundColor = Color(0xFF43C5A5),
+ contentColor = Color.White
+ )
- Provider.ANONYMOUS -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_anonymous_white_24dp),
- backgroundColor = Color(0xFFF4B400),
- contentColor = Color.White
- )
- }
+ val Anonymous = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_anonymous_white_24dp),
+ backgroundColor = Color(0xFFF4B400),
+ contentColor = Color.White
+ )
- Provider.MICROSOFT -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_microsoft_24dp),
- backgroundColor = Color(0xFF2F2F2F),
- contentColor = Color.White
- )
- }
+ val Microsoft = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_microsoft_24dp),
+ backgroundColor = Color(0xFF2F2F2F),
+ contentColor = Color.White
+ )
- Provider.YAHOO -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_yahoo_24dp),
- backgroundColor = Color(0xFF720E9E),
- contentColor = Color.White
- )
- }
+ val Yahoo = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_yahoo_24dp),
+ backgroundColor = Color(0xFF720E9E),
+ contentColor = Color.White
+ )
- Provider.APPLE -> {
- provider.id to AuthUITheme.ProviderStyle(
- icon = AuthUIAsset.Resource(R.drawable.fui_ic_apple_white_24dp),
- backgroundColor = Color.Black,
- contentColor = Color.White
- )
- }
- }
- }
+ val Apple = AuthUITheme.ProviderStyle(
+ icon = AuthUIAsset.Resource(R.drawable.fui_ic_apple_white_24dp),
+ backgroundColor = Color.Black,
+ contentColor = Color.White
+ )
+
+ val default: Map
+ get() = mapOf(
+ Provider.GOOGLE.id to Google,
+ Provider.FACEBOOK.id to Facebook,
+ Provider.TWITTER.id to Twitter,
+ Provider.GITHUB.id to Github,
+ Provider.EMAIL.id to Email,
+ Provider.PHONE.id to Phone,
+ Provider.ANONYMOUS.id to Anonymous,
+ Provider.MICROSOFT.id to Microsoft,
+ Provider.YAHOO.id to Yahoo,
+ Provider.APPLE.id to Apple
+ )
}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt
index 65e0ac0c0..c83f9a280 100644
--- a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt
@@ -25,6 +25,24 @@ import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.exceptions.NoCredentialException
+import com.firebase.ui.auth.util.CredentialPersistenceManager
+
+/**
+ * Provider interface for obtaining CredentialManager instances.
+ * This allows test code to inject mock CredentialManager instances.
+ */
+interface CredentialManagerProvider {
+ fun getCredentialManager(context: Context): CredentialManager
+}
+
+/**
+ * Default implementation that creates a real CredentialManager instance.
+ */
+class DefaultCredentialManagerProvider : CredentialManagerProvider {
+ override fun getCredentialManager(context: Context): CredentialManager {
+ return CredentialManager.create(context)
+ }
+}
/**
* Handler for password credential operations using Android's Credential Manager.
@@ -33,11 +51,53 @@ import androidx.credentials.exceptions.NoCredentialException
* the system credential manager, which displays native UI prompts to the user.
*
* @property context The Android context used for credential operations
+ * @property provider Optional provider for testing purposes
*/
class PasswordCredentialHandler(
- private val context: Context
+ private val context: Context,
+ provider: CredentialManagerProvider? = null
) {
- private val credentialManager: CredentialManager = CredentialManager.create(context)
+ companion object {
+ /**
+ * Test-only provider for injecting mock CredentialManager instances.
+ * Set this in your test setup to override the default CredentialManager.
+ *
+ * Example:
+ * ```
+ * PasswordCredentialHandler.testCredentialManagerProvider = object : CredentialManagerProvider {
+ * override fun getCredentialManager(context: Context) = mockCredentialManager
+ * }
+ * ```
+ */
+ @Volatile
+ var testCredentialManagerProvider: CredentialManagerProvider? = null
+
+ /**
+ * Checks if credentials have been saved at least once.
+ * This prevents unnecessary credential retrieval attempts.
+ *
+ * @param context The Android context
+ * @return true if credentials have been saved, false otherwise
+ */
+ suspend fun hasSavedCredentials(context: Context): Boolean {
+ return CredentialPersistenceManager.hasSavedCredentials(context)
+ }
+
+ /**
+ * Clears the saved credentials flag.
+ * Useful for testing or when user signs out permanently.
+ *
+ * @param context The Android context
+ */
+ suspend fun clearSavedCredentialsFlag(context: Context) {
+ CredentialPersistenceManager.clearSavedCredentialsFlag(context)
+ }
+ }
+
+ private val credentialManager: CredentialManager =
+ provider?.getCredentialManager(context)
+ ?: testCredentialManagerProvider?.getCredentialManager(context)
+ ?: CredentialManager.create(context)
/**
* Saves a password credential to the system credential manager.
@@ -62,6 +122,8 @@ class PasswordCredentialHandler(
try {
credentialManager.createCredential(context, request)
+ // Mark that credentials have been saved successfully
+ CredentialPersistenceManager.setCredentialsSaved(context)
} catch (e: CreateCredentialCancellationException) {
// User cancelled the save operation
throw PasswordCredentialCancelledException("User cancelled password save operation", e)
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/components/AuthProviderButton.kt b/auth/src/main/java/com/firebase/ui/auth/ui/components/AuthProviderButton.kt
index 29a406821..9a3f5e1a2 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/components/AuthProviderButton.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/components/AuthProviderButton.kt
@@ -14,6 +14,7 @@
package com.firebase.ui.auth.ui.components
+import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -29,11 +30,13 @@ import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -44,6 +47,8 @@ import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.configuration.theme.AuthUITheme
+import com.firebase.ui.auth.configuration.theme.LocalAuthUITheme
+import com.firebase.ui.auth.configuration.theme.ProviderStyleDefaults
/**
* A customizable button for an authentication provider.
@@ -67,6 +72,8 @@ import com.firebase.ui.auth.configuration.theme.AuthUITheme
* @param enabled If the button is enabled. Defaults to true.
* @param style Optional custom styling for the button.
* @param stringProvider The [AuthUIStringProvider] for localized strings
+ * @param subtitle Optional subtitle text to display below the provider label (e.g., user email)
+ * @param label Optional custom label to override the default provider label
*
* @since 10.0.0
*/
@@ -78,19 +85,32 @@ fun AuthProviderButton(
enabled: Boolean = true,
style: AuthUITheme.ProviderStyle? = null,
stringProvider: AuthUIStringProvider,
+ subtitle: String? = null,
+ label: String? = null,
+ showAsContinue: Boolean = false,
) {
val context = LocalContext.current
- val providerStyle = resolveProviderStyle(provider, style)
- val providerLabel = resolveProviderLabel(provider, stringProvider, context)
+ val authTheme = LocalAuthUITheme.current
+ val providerLabel =
+ label ?: resolveProviderLabel(provider, stringProvider, context, showAsContinue)
+ val providerStyle = resolveProviderStyle(
+ provider = provider,
+ style = style,
+ providerStyles = authTheme.providerStyles,
+ defaultButtonShape = authTheme.providerButtonShape
+ )
Button(
modifier = modifier,
- contentPadding = PaddingValues(horizontal = 12.dp),
+ contentPadding = PaddingValues(
+ horizontal = 12.dp,
+ vertical = if (subtitle != null) 12.dp else 8.dp
+ ),
colors = ButtonDefaults.buttonColors(
containerColor = providerStyle.backgroundColor,
contentColor = providerStyle.contentColor,
),
- shape = providerStyle.shape,
+ shape = providerStyle.shape ?: RoundedCornerShape(4.dp),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = providerStyle.elevation
),
@@ -123,11 +143,30 @@ fun AuthProviderButton(
}
Spacer(modifier = Modifier.width(12.dp))
}
- Text(
- text = providerLabel,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
+
+ if (subtitle != null) {
+ Column(
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = providerLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+ Text(
+ text = subtitle,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.bodySmall,
+ )
+ }
+ } else {
+ Text(
+ text = providerLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+ }
}
}
}
@@ -135,27 +174,47 @@ fun AuthProviderButton(
internal fun resolveProviderStyle(
provider: AuthProvider,
style: AuthUITheme.ProviderStyle?,
+ providerStyles: Map,
+ defaultButtonShape: Shape?,
): AuthUITheme.ProviderStyle {
- if (style != null) return style
+ // If explicit style is provided, use it but apply default shape if needed
+ if (style != null) {
+ return if (style.shape == null) {
+ style.copy(shape = defaultButtonShape ?: RoundedCornerShape(4.dp))
+ } else {
+ style
+ }
+ }
- val defaultStyle =
- AuthUITheme.Default.providerStyles[provider.providerId] ?: AuthUITheme.ProviderStyle.Empty
+ // Get the configured style from the theme or fall back to defaults
+ val configuredStyle = providerStyles[provider.providerId]
+ ?: ProviderStyleDefaults.default[provider.providerId]
+ ?: AuthUITheme.ProviderStyle.Empty
- return if (provider is AuthProvider.GenericOAuth) {
- AuthUITheme.ProviderStyle(
- icon = provider.buttonIcon ?: defaultStyle.icon,
- backgroundColor = provider.buttonColor ?: defaultStyle.backgroundColor,
- contentColor = provider.contentColor ?: defaultStyle.contentColor,
+ // Handle GenericOAuth providers with custom properties
+ val resolvedStyle = if (provider is AuthProvider.GenericOAuth) {
+ configuredStyle.copy(
+ icon = provider.buttonIcon ?: configuredStyle.icon,
+ backgroundColor = provider.buttonColor ?: configuredStyle.backgroundColor,
+ contentColor = provider.contentColor ?: configuredStyle.contentColor,
)
} else {
- defaultStyle
+ configuredStyle
+ }
+
+ // Apply default button shape if no shape is explicitly set
+ return if (resolvedStyle.shape == null) {
+ resolvedStyle.copy(shape = defaultButtonShape ?: RoundedCornerShape(4.dp))
+ } else {
+ resolvedStyle
}
}
internal fun resolveProviderLabel(
provider: AuthProvider,
stringProvider: AuthUIStringProvider,
- context: android.content.Context
+ context: Context,
+ showAsContinue: Boolean = false,
): String = when (provider) {
is AuthProvider.GenericOAuth -> provider.buttonLabel
is AuthProvider.Apple -> {
@@ -163,22 +222,23 @@ internal fun resolveProviderLabel(
if (provider.locale != null) {
val appleLocale = java.util.Locale.forLanguageTag(provider.locale)
val appleStringProvider = DefaultAuthUIStringProvider(context, appleLocale)
- appleStringProvider.signInWithApple
+ if (showAsContinue) appleStringProvider.continueWithApple else appleStringProvider.signInWithApple
} else {
- stringProvider.signInWithApple
+ if (showAsContinue) stringProvider.continueWithApple else stringProvider.signInWithApple
}
}
+
else -> when (Provider.fromId(provider.providerId)) {
- Provider.GOOGLE -> stringProvider.signInWithGoogle
- Provider.FACEBOOK -> stringProvider.signInWithFacebook
- Provider.TWITTER -> stringProvider.signInWithTwitter
- Provider.GITHUB -> stringProvider.signInWithGithub
- Provider.EMAIL -> stringProvider.signInWithEmail
- Provider.PHONE -> stringProvider.signInWithPhone
+ Provider.GOOGLE -> if (showAsContinue) stringProvider.continueWithGoogle else stringProvider.signInWithGoogle
+ Provider.FACEBOOK -> if (showAsContinue) stringProvider.continueWithFacebook else stringProvider.signInWithFacebook
+ Provider.TWITTER -> if (showAsContinue) stringProvider.continueWithTwitter else stringProvider.signInWithTwitter
+ Provider.GITHUB -> if (showAsContinue) stringProvider.continueWithGithub else stringProvider.signInWithGithub
+ Provider.EMAIL -> if (showAsContinue) stringProvider.continueWithEmail else stringProvider.signInWithEmail
+ Provider.PHONE -> if (showAsContinue) stringProvider.continueWithPhone else stringProvider.signInWithPhone
Provider.ANONYMOUS -> stringProvider.signInAnonymously
- Provider.MICROSOFT -> stringProvider.signInWithMicrosoft
- Provider.YAHOO -> stringProvider.signInWithYahoo
- Provider.APPLE -> stringProvider.signInWithApple
+ Provider.MICROSOFT -> if (showAsContinue) stringProvider.continueWithMicrosoft else stringProvider.signInWithMicrosoft
+ Provider.YAHOO -> if (showAsContinue) stringProvider.continueWithYahoo else stringProvider.signInWithYahoo
+ Provider.APPLE -> if (showAsContinue) stringProvider.continueWithApple else stringProvider.signInWithApple
null -> "Unknown Provider"
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialog.kt b/auth/src/main/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialog.kt
index 96c2e7978..6d4aef708 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialog.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialog.kt
@@ -126,7 +126,11 @@ private fun getRecoveryMessage(
): String {
return when (error) {
is AuthException.NetworkException -> stringProvider.networkErrorRecoveryMessage
- is AuthException.InvalidCredentialsException -> stringProvider.invalidCredentialsRecoveryMessage
+ is AuthException.InvalidCredentialsException -> {
+ // Use the actual error message from Firebase if available, otherwise fallback to generic message
+ error.message?.takeIf { it.isNotBlank() && it != "Invalid credentials provided" }
+ ?: stringProvider.invalidCredentialsRecoveryMessage
+ }
is AuthException.UserNotFoundException -> stringProvider.userNotFoundRecoveryMessage
is AuthException.WeakPasswordException -> {
// Include specific reason if available
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 5c69b19bc..bf4c3b6a5 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
@@ -18,11 +18,17 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -34,9 +40,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.firebase.ui.auth.R
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
+import com.firebase.ui.auth.configuration.auth_provider.Provider
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.ui.components.AuthProviderButton
+import com.firebase.ui.auth.util.SignInPreferenceManager
/**
* Renders the provider selection screen.
@@ -59,6 +67,7 @@ import com.firebase.ui.auth.ui.components.AuthProviderButton
* @param customLayout An optional custom layout composable for the provider buttons.
* @param termsOfServiceUrl The URL for the Terms of Service.
* @param privacyPolicyUrl The URL for the Privacy Policy.
+ * @param lastSignInPreference The last sign-in preference to show a "Continue as..." button.
*
* @since 10.0.0
*/
@@ -71,6 +80,7 @@ fun AuthMethodPicker(
customLayout: @Composable ((List, (AuthProvider) -> Unit) -> Unit)? = null,
termsOfServiceUrl: String? = null,
privacyPolicyUrl: String? = null,
+ lastSignInPreference: SignInPreferenceManager.SignInPreference? = null,
) {
val context = LocalContext.current
val inPreview = LocalInspectionMode.current
@@ -103,6 +113,37 @@ fun AuthMethodPicker(
.testTag("AuthMethodPicker LazyColumn"),
horizontalAlignment = Alignment.CenterHorizontally,
) {
+ // Show "Continue as..." button if last sign-in preference exists
+ lastSignInPreference?.let { preference ->
+ val lastProvider = providers.find { it.providerId == preference.providerId }
+ if (lastProvider != null) {
+ item {
+ ContinueAsButton(
+ provider = lastProvider,
+ identifier = preference.identifier,
+ onClick = { onProviderSelected(lastProvider) }
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // Divider with "or"
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ HorizontalDivider(modifier = Modifier.weight(1f))
+ Text(
+ text = stringProvider.orContinueWith,
+ modifier = Modifier.padding(horizontal = 8.dp),
+ style = MaterialTheme.typography.bodySmall
+ )
+ HorizontalDivider(modifier = Modifier.weight(1f))
+ }
+ Spacer(modifier = Modifier.height(24.dp))
+ }
+ }
+ }
+
+ // Show all providers
itemsIndexed(providers) { index, provider ->
Box(
modifier = Modifier
@@ -139,6 +180,33 @@ 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 onClick Callback when the button is clicked
+ */
+@Composable
+private fun ContinueAsButton(
+ provider: AuthProvider,
+ identifier: String?,
+ onClick: () -> Unit
+) {
+ val stringProvider = LocalAuthUIStringProvider.current
+
+ AuthProviderButton(
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag("ContinueAsButton"),
+ onClick = onClick,
+ provider = provider,
+ stringProvider = stringProvider,
+ subtitle = identifier,
+ showAsContinue = true
+ )
+}
+
@Preview(showBackground = true)
@Composable
fun PreviewAuthMethodPicker() {
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 6e9ed8492..6b3bf5d38 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
@@ -16,6 +16,9 @@ package com.firebase.ui.auth.ui.screens
import android.util.Log
import androidx.activity.compose.LocalActivity
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -61,12 +64,14 @@ import com.firebase.ui.auth.configuration.auth_provider.signInWithEmailLink
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 com.firebase.ui.auth.configuration.theme.LocalAuthUITheme
import com.firebase.ui.auth.ui.components.LocalTopLevelDialogController
import com.firebase.ui.auth.ui.components.rememberTopLevelDialogController
import com.firebase.ui.auth.ui.method_picker.AuthMethodPicker
import com.firebase.ui.auth.ui.screens.email.EmailAuthScreen
import com.firebase.ui.auth.ui.screens.phone.PhoneAuthScreen
import com.firebase.ui.auth.util.EmailLinkPersistenceManager
+import com.firebase.ui.auth.util.SignInPreferenceManager
import com.google.firebase.auth.AuthCredential
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.MultiFactorResolver
@@ -112,6 +117,13 @@ fun FirebaseAuthScreen(
val pendingLinkingCredential = remember { mutableStateOf(null) }
val pendingResolver = remember { mutableStateOf(null) }
val emailLinkFromDifferentDevice = remember { mutableStateOf(null) }
+ val lastSignInPreference =
+ remember { mutableStateOf(null) }
+
+ // Load last sign-in preference on launch
+ LaunchedEffect(authState) {
+ lastSignInPreference.value = SignInPreferenceManager.getLastSignIn(context)
+ }
val anonymousProvider =
configuration.providers.filterIsInstance().firstOrNull()
@@ -155,6 +167,7 @@ fun FirebaseAuthScreen(
val onSignInWithApple = appleProvider?.let {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -163,6 +176,7 @@ fun FirebaseAuthScreen(
val onSignInWithGithub = githubProvider?.let {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -171,6 +185,7 @@ fun FirebaseAuthScreen(
val onSignInWithMicrosoft = microsoftProvider?.let {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -179,6 +194,7 @@ fun FirebaseAuthScreen(
val onSignInWithYahoo = yahooProvider?.let {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -187,6 +203,7 @@ fun FirebaseAuthScreen(
val onSignInWithTwitter = twitterProvider?.let {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -195,6 +212,7 @@ fun FirebaseAuthScreen(
val genericOAuthHandlers = genericOAuthProviders.associateWith {
authUI.rememberOAuthSignInHandler(
+ context = context,
activity = activity,
config = configuration,
provider = it
@@ -203,7 +221,8 @@ fun FirebaseAuthScreen(
CompositionLocalProvider(
LocalAuthUIStringProvider provides configuration.stringProvider,
- LocalTopLevelDialogController provides dialogController
+ LocalTopLevelDialogController provides dialogController,
+ LocalAuthUITheme provides (configuration.theme ?: LocalAuthUITheme.current)
) {
Surface(
modifier = Modifier
@@ -211,7 +230,19 @@ fun FirebaseAuthScreen(
) {
NavHost(
navController = navController,
- startDestination = AuthRoute.MethodPicker.route
+ startDestination = AuthRoute.MethodPicker.route,
+ enterTransition = configuration.transitions?.enterTransition ?: {
+ fadeIn(animationSpec = tween(700))
+ },
+ exitTransition = configuration.transitions?.exitTransition ?: {
+ fadeOut(animationSpec = tween(700))
+ },
+ popEnterTransition = configuration.transitions?.popEnterTransition ?: {
+ fadeIn(animationSpec = tween(700))
+ },
+ popExitTransition = configuration.transitions?.popExitTransition ?: {
+ fadeOut(animationSpec = tween(700))
+ }
) {
composable(AuthRoute.MethodPicker.route) {
Scaffold { innerPadding ->
@@ -222,6 +253,7 @@ fun FirebaseAuthScreen(
logo = logoAsset,
termsOfServiceUrl = configuration.tosUrl,
privacyPolicyUrl = configuration.privacyPolicyUrl,
+ lastSignInPreference = lastSignInPreference.value,
onProviderSelected = { provider ->
when (provider) {
is AuthProvider.Anonymous -> onSignInAnonymously?.invoke()
@@ -320,6 +352,7 @@ fun FirebaseAuthScreen(
coroutineScope.launch {
try {
authUI.signOut(context)
+ // Keep sign-in preference for "Continue as..." on next launch
} catch (e: Exception) {
onSignInFailure(AuthException.from(e))
} finally {
@@ -432,7 +465,8 @@ fun FirebaseAuthScreen(
if (emailLink != null && emailProvider != null) {
try {
// Try to retrieve saved email from DataStore (same-device flow)
- val savedEmail = EmailLinkPersistenceManager.default.retrieveSessionRecord(context)?.email
+ val savedEmail =
+ EmailLinkPersistenceManager.default.retrieveSessionRecord(context)?.email
if (savedEmail != null) {
// Same device - we have the email, sign in automatically
@@ -474,6 +508,12 @@ fun FirebaseAuthScreen(
if (state.user.uid != lastSuccessfulUserId.value) {
onSignInSuccess(result)
lastSuccessfulUserId.value = state.user.uid
+
+ // Reload sign-in preference (may have been updated by provider)
+ coroutineScope.launch {
+ lastSignInPreference.value =
+ SignInPreferenceManager.getLastSignIn(context)
+ }
}
}
@@ -560,26 +600,26 @@ fun FirebaseAuthScreen(
is AuthException.AccountLinkingRequiredException -> {
pendingLinkingCredential.value = exception.credential
- navController.navigate(AuthRoute.Email.route) {
- launchSingleTop = true
+ navController.navigate(AuthRoute.Email.route) {
+ launchSingleTop = true
+ }
}
- }
- is AuthException.EmailLinkPromptForEmailException -> {
- // Cross-device flow: User needs to enter their email
- emailLinkFromDifferentDevice.value = exception.emailLink
- navController.navigate(AuthRoute.Email.route) {
- launchSingleTop = true
+ is AuthException.EmailLinkPromptForEmailException -> {
+ // Cross-device flow: User needs to enter their email
+ emailLinkFromDifferentDevice.value = exception.emailLink
+ navController.navigate(AuthRoute.Email.route) {
+ launchSingleTop = true
+ }
}
- }
- is AuthException.EmailLinkCrossDeviceLinkingException -> {
- // Cross-device linking flow: User needs to enter email to link provider
- emailLinkFromDifferentDevice.value = exception.emailLink
- navController.navigate(AuthRoute.Email.route) {
- launchSingleTop = true
+ is AuthException.EmailLinkCrossDeviceLinkingException -> {
+ // Cross-device linking flow: User needs to enter email to link provider
+ emailLinkFromDifferentDevice.value = exception.emailLink
+ navController.navigate(AuthRoute.Email.route) {
+ launchSingleTop = true
+ }
}
- }
else -> Unit
}
@@ -624,7 +664,7 @@ data class AuthSuccessUiContext(
private fun SuccessDestination(
authState: AuthState,
stringProvider: AuthUIStringProvider,
- uiContext: AuthSuccessUiContext
+ uiContext: AuthSuccessUiContext,
) {
when (authState) {
is AuthState.Success -> {
@@ -669,7 +709,7 @@ private fun AuthSuccessContent(
authUI: FirebaseAuthUI,
stringProvider: AuthUIStringProvider,
onSignOut: () -> Unit,
- onManageMfa: () -> Unit
+ onManageMfa: () -> Unit,
) {
val user = authUI.getCurrentUser()
val userIdentifier = user?.email ?: user?.phoneNumber ?: user?.uid.orEmpty()
@@ -702,7 +742,7 @@ private fun EmailVerificationContent(
authUI: FirebaseAuthUI,
stringProvider: AuthUIStringProvider,
onCheckStatus: () -> Unit,
- onSignOut: () -> Unit
+ onSignOut: () -> Unit,
) {
val user = authUI.getCurrentUser()
val emailLabel = user?.email ?: stringProvider.emailProvider
@@ -734,7 +774,7 @@ private fun EmailVerificationContent(
@Composable
private fun ProfileCompletionContent(
missingFields: List,
- stringProvider: AuthUIStringProvider
+ stringProvider: AuthUIStringProvider,
) {
Column(
modifier = Modifier.fillMaxSize(),
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 3cc3030f2..2ebc2542f 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
@@ -35,6 +35,10 @@ import com.firebase.ui.auth.configuration.auth_provider.sendSignInLinkToEmail
import com.firebase.ui.auth.configuration.auth_provider.signInWithEmailAndPassword
import com.firebase.ui.auth.configuration.auth_provider.signInWithEmailLink
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialCancelledException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialHandler
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialNotFoundException
import com.firebase.ui.auth.ui.components.LocalTopLevelDialogController
import com.google.firebase.auth.AuthCredential
import com.google.firebase.auth.AuthResult
@@ -95,6 +99,7 @@ class EmailAuthContentState(
val onConfirmPasswordChange: (String) -> Unit,
val displayName: String,
val onDisplayNameChange: (String) -> Unit,
+ val onRetrievedCredential: (Pair) -> Unit,
val onSignInClick: () -> Unit,
val onSignInEmailLinkClick: () -> Unit,
val onSignUpClick: () -> Unit,
@@ -163,6 +168,9 @@ fun EmailAuthScreen(
val resetLinkSent = authState is AuthState.PasswordResetLinkSent
val emailSignInLinkSent = authState is AuthState.EmailSignInLinkSent
+ // Track if credentials were retrieved from Credential Manager
+ val retrievedCredential = remember { mutableStateOf?>(null) }
+
LaunchedEffect(authState) {
Log.d("EmailAuthScreen", "Current state: $authState")
when (val state = authState) {
@@ -237,15 +245,24 @@ fun EmailAuthScreen(
onDisplayNameChange = { displayName ->
displayNameValue.value = displayName
},
+ onRetrievedCredential = { credential ->
+ retrievedCredential.value = credential
+ },
onSignInClick = {
coroutineScope.launch {
try {
+ // Check if user is signing in with retrieved credentials
+ val isUsingRetrievedCredential = retrievedCredential.value?.let { (email, password) ->
+ email == emailTextValue.value && password == passwordTextValue.value
+ } ?: false
+
authUI.signInWithEmailAndPassword(
context = context,
config = configuration,
email = emailTextValue.value,
password = passwordTextValue.value,
credentialForLinking = authCredentialForLinking,
+ skipCredentialSave = isUsingRetrievedCredential
)
} catch (e: Exception) {
onError(AuthException.from(e))
@@ -350,6 +367,7 @@ private fun DefaultEmailAuthContent(
password = state.password,
onEmailChange = state.onEmailChange,
onPasswordChange = state.onPasswordChange,
+ onRetrievedCredential = state.onRetrievedCredential,
onSignInClick = state.onSignInClick,
onGoToSignUp = state.onGoToSignUp,
onGoToResetPassword = state.onGoToResetPassword,
@@ -385,7 +403,8 @@ private fun DefaultEmailAuthContent(
onPasswordChange = state.onPasswordChange,
onConfirmPasswordChange = state.onConfirmPasswordChange,
onSignUpClick = state.onSignUpClick,
- onGoToSignIn = state.onGoToSignIn
+ onGoToSignIn = state.onGoToSignIn,
+ onNavigateBack = onCancel
)
}
@@ -397,7 +416,8 @@ private fun DefaultEmailAuthContent(
resetLinkSent = state.resetLinkSent,
onEmailChange = state.onEmailChange,
onSendResetLink = state.onSendResetLinkClick,
- onGoToSignIn = state.onGoToSignIn
+ onGoToSignIn = state.onGoToSignIn,
+ onNavigateBack = onCancel
)
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt
index baf9e1485..8b73f2c2d 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt
@@ -24,10 +24,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -63,6 +67,7 @@ fun ResetPasswordUI(
onEmailChange: (String) -> Unit,
onSendResetLink: () -> Unit,
onGoToSignIn: () -> Unit,
+ onNavigateBack: (() -> Unit)? = null,
) {
val context = LocalContext.current
@@ -115,6 +120,16 @@ fun ResetPasswordUI(
title = {
Text(stringProvider.recoverPasswordPageTitle)
},
+ navigationIcon = {
+ if (onNavigateBack != null) {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringProvider.backAction
+ )
+ }
+ }
+ },
colors = AuthUITheme.topAppBarColors
)
},
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt
index 962be4160..eabde87a1 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt
@@ -14,6 +14,7 @@
package com.firebase.ui.auth.ui.screens.email
+import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -46,7 +47,9 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -65,6 +68,10 @@ import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvi
import com.firebase.ui.auth.configuration.theme.AuthUITheme
import com.firebase.ui.auth.configuration.validators.EmailValidator
import com.firebase.ui.auth.configuration.validators.PasswordValidator
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialCancelledException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialException
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialHandler
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialNotFoundException
import com.firebase.ui.auth.ui.components.AuthTextField
import com.firebase.ui.auth.ui.components.LocalTopLevelDialogController
import com.firebase.ui.auth.ui.components.TermsAndPrivacyForm
@@ -80,12 +87,14 @@ fun SignInUI(
password: String,
onEmailChange: (String) -> Unit,
onPasswordChange: (String) -> Unit,
+ onRetrievedCredential: (Pair) -> Unit,
onSignInClick: () -> Unit,
onGoToSignUp: () -> Unit,
onGoToResetPassword: () -> Unit,
onGoToEmailLinkSignIn: () -> Unit,
onNavigateBack: (() -> Unit)? = null,
) {
+ val context = LocalContext.current
val provider = configuration.providers.filterIsInstance().first()
val stringProvider = LocalAuthUIStringProvider.current
val emailValidator = remember { EmailValidator(stringProvider) }
@@ -102,6 +111,45 @@ fun SignInUI(
}
}
+ // Retrieve saved credentials when in SignIn mode
+ val credentialRetrievalAttempted = remember { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ if (configuration.isCredentialManagerEnabled &&
+ !credentialRetrievalAttempted.value &&
+ PasswordCredentialHandler.hasSavedCredentials(context)) {
+ credentialRetrievalAttempted.value = true
+
+ try {
+ val credentialHandler = PasswordCredentialHandler(context)
+ val credential = credentialHandler.getPassword()
+
+ Log.d("EmailAuthScreen", "Retrieved credential for: ${credential.username}")
+
+ // Auto-fill the email and password fields
+ onEmailChange(credential.username)
+ onPasswordChange(credential.password)
+
+ emailValidator.validate(credential.username)
+ passwordValidator.validate(credential.password)
+
+ // Store retrieved credential to compare later
+ onRetrievedCredential(Pair(credential.username, credential.password))
+
+ onSignInClick()
+ } catch (e: PasswordCredentialNotFoundException) {
+ Log.d("EmailAuthScreen", "No saved credentials found")
+ // No credentials saved - user will enter manually
+ } catch (e: PasswordCredentialCancelledException) {
+ Log.d("EmailAuthScreen", "User cancelled credential selection")
+ // User cancelled - let them enter manually
+ } catch (e: PasswordCredentialException) {
+ Log.w("EmailAuthScreen", "Failed to retrieve credentials", e)
+ // Failed to retrieve - let them enter manually
+ }
+ }
+ }
+
Scaffold(
modifier = modifier,
topBar = {
@@ -289,6 +337,7 @@ fun PreviewSignInUI() {
emailSignInLinkSent = false,
onEmailChange = { email -> },
onPasswordChange = { password -> },
+ onRetrievedCredential = { credential -> },
onSignInClick = {},
onGoToSignUp = {},
onGoToResetPassword = {},
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt
index 152931f13..dfc413bc6 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt
@@ -24,9 +24,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
@@ -65,6 +69,7 @@ fun SignUpUI(
onConfirmPasswordChange: (String) -> Unit,
onGoToSignIn: () -> Unit,
onSignUpClick: () -> Unit,
+ onNavigateBack: (() -> Unit)? = null,
) {
val provider = configuration.providers.filterIsInstance().first()
val context = LocalContext.current
@@ -105,6 +110,16 @@ fun SignUpUI(
title = {
Text(stringProvider.signupPageTitle)
},
+ navigationIcon = {
+ if (onNavigateBack != null) {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringProvider.backAction
+ )
+ }
+ }
+ },
colors = AuthUITheme.topAppBarColors
)
},
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt
index 9839a7958..122e73a87 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt
@@ -24,9 +24,13 @@ import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -66,6 +70,7 @@ fun EnterVerificationCodeUI(
onResendCodeClick: () -> Unit,
onChangeNumberClick: () -> Unit,
title: String? = null,
+ onNavigateBack: (() -> Unit)? = null,
) {
val context = LocalContext.current
val stringProvider = LocalAuthUIStringProvider.current
@@ -88,6 +93,16 @@ fun EnterVerificationCodeUI(
title = {
Text(title ?: stringProvider.verifyPhoneNumber)
},
+ navigationIcon = {
+ if (onNavigateBack != null) {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringProvider.backAction
+ )
+ }
+ }
+ },
colors = AuthUITheme.topAppBarColors
)
},
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt
index 300b2112e..0fa4c0f0a 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt
@@ -193,6 +193,7 @@ fun PhoneAuthScreen(
coroutineScope.launch {
try {
authUI.signInWithPhoneAuthCredential(
+ context = context,
config = configuration,
credential = state.credential
)
@@ -265,6 +266,7 @@ fun PhoneAuthScreen(
try {
verificationId.value?.let { id ->
authUI.submitVerificationCode(
+ context = context,
config = configuration,
verificationId = id,
code = verificationCodeValue.value
@@ -344,7 +346,8 @@ private fun DefaultPhoneAuthContent(
onVerificationCodeChange = state.onVerificationCodeChange,
onVerifyCodeClick = state.onVerifyCodeClick,
onResendCodeClick = state.onResendCodeClick,
- onChangeNumberClick = state.onChangeNumberClick
+ onChangeNumberClick = state.onChangeNumberClick,
+ onNavigateBack = onCancel
)
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/CredentialPersistenceManager.kt b/auth/src/main/java/com/firebase/ui/auth/util/CredentialPersistenceManager.kt
new file mode 100644
index 000000000..e62534409
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/util/CredentialPersistenceManager.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.util
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.first
+
+private val Context.credentialDataStore: DataStore by preferencesDataStore(
+ name = "com.firebase.ui.auth.util.CredentialPersistenceManager"
+)
+
+/**
+ * Manages persistence for credential manager state.
+ *
+ * This class tracks whether credentials have been saved to the Android Credential Manager
+ * to prevent unnecessary credential retrieval attempts when no credentials exist.
+ *
+ * @since 10.0.0
+ */
+object CredentialPersistenceManager {
+
+ private val KEY_HAS_SAVED_CREDENTIALS = booleanPreferencesKey("has_saved_credentials")
+
+ /**
+ * Marks that credentials have been successfully saved to the credential manager.
+ *
+ * @param context The Android context
+ */
+ suspend fun setCredentialsSaved(context: Context) {
+ context.credentialDataStore.edit { prefs ->
+ prefs[KEY_HAS_SAVED_CREDENTIALS] = true
+ }
+ }
+
+ /**
+ * Checks if credentials have been saved at least once.
+ * This prevents unnecessary credential retrieval attempts.
+ *
+ * @param context The Android context
+ * @return true if credentials have been saved, false otherwise
+ */
+ suspend fun hasSavedCredentials(context: Context): Boolean {
+ val prefs = context.credentialDataStore.data.first()
+ return prefs[KEY_HAS_SAVED_CREDENTIALS] ?: false
+ }
+
+ /**
+ * Clears the saved credentials flag.
+ * Useful for testing or when user signs out permanently.
+ *
+ * @param context The Android context
+ */
+ suspend fun clearSavedCredentialsFlag(context: Context) {
+ context.credentialDataStore.edit { prefs ->
+ prefs.remove(KEY_HAS_SAVED_CREDENTIALS)
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/SignInPreferenceManager.kt b/auth/src/main/java/com/firebase/ui/auth/util/SignInPreferenceManager.kt
new file mode 100644
index 000000000..7bfcc10f4
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/util/SignInPreferenceManager.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.util
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.longPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.first
+
+private val Context.signInPreferenceDataStore: DataStore by preferencesDataStore(
+ name = "com.firebase.ui.auth.util.SignInPreferenceManager"
+)
+
+/**
+ * Manages persistence for the last-used sign-in method.
+ *
+ * This class tracks which authentication provider was last used to sign in,
+ * along with the user identifier (email, phone number, etc.). This enables
+ * a better UX by showing "Continue as [identifier]" with the last-used provider
+ * prominently on the method picker screen.
+ *
+ * @since 10.0.0
+ */
+object SignInPreferenceManager {
+
+ private val KEY_LAST_PROVIDER_ID = stringPreferencesKey("last_provider_id")
+ private val KEY_LAST_IDENTIFIER = stringPreferencesKey("last_identifier")
+ private val KEY_LAST_TIMESTAMP = longPreferencesKey("last_timestamp")
+
+ /**
+ * Saves the last-used sign-in method and user identifier.
+ *
+ * This should be called after a successful sign-in to track the user's
+ * preferred sign-in method.
+ *
+ * @param context The Android context
+ * @param providerId The provider ID (e.g., "google.com", "facebook.com", "password", "phone")
+ * @param identifier The user identifier (email for social/email auth, phone number for phone auth)
+ */
+ suspend fun saveLastSignIn(
+ context: Context,
+ providerId: String,
+ identifier: String?
+ ) {
+ context.signInPreferenceDataStore.edit { prefs ->
+ prefs[KEY_LAST_PROVIDER_ID] = providerId
+ identifier?.let { prefs[KEY_LAST_IDENTIFIER] = it }
+ prefs[KEY_LAST_TIMESTAMP] = System.currentTimeMillis()
+ }
+ }
+
+ /**
+ * Retrieves the last-used sign-in preference.
+ *
+ * @param context The Android context
+ * @return [SignInPreference] containing the last-used provider and identifier, or null if none
+ */
+ suspend fun getLastSignIn(context: Context): SignInPreference? {
+ val prefs = context.signInPreferenceDataStore.data.first()
+ val providerId = prefs[KEY_LAST_PROVIDER_ID]
+ val identifier = prefs[KEY_LAST_IDENTIFIER]
+ val timestamp = prefs[KEY_LAST_TIMESTAMP]
+
+ return if (providerId != null && timestamp != null) {
+ SignInPreference(
+ providerId = providerId,
+ identifier = identifier,
+ timestamp = timestamp
+ )
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Clears the saved sign-in preference.
+ *
+ * This should be called when the user signs out permanently or
+ * when resetting authentication state.
+ *
+ * @param context The Android context
+ */
+ suspend fun clearLastSignIn(context: Context) {
+ context.signInPreferenceDataStore.edit { prefs ->
+ prefs.remove(KEY_LAST_PROVIDER_ID)
+ prefs.remove(KEY_LAST_IDENTIFIER)
+ prefs.remove(KEY_LAST_TIMESTAMP)
+ }
+ }
+
+ /**
+ * Data class representing a saved sign-in preference.
+ *
+ * @property providerId The provider ID (e.g., "google.com", "facebook.com", "password", "phone")
+ * @property identifier The user identifier (email, phone number, etc.), may be null
+ * @property timestamp The timestamp when this preference was saved
+ */
+ data class SignInPreference(
+ val providerId: String,
+ val identifier: String?,
+ val timestamp: Long
+ )
+}
diff --git a/auth/src/main/res/values-ar/strings.xml b/auth/src/main/res/values-ar/strings.xml
index ba6e8ca42..dcf4ca88c 100755
--- a/auth/src/main/res/values-ar/strings.xml
+++ b/auth/src/main/res/values-ar/strings.xml
@@ -12,15 +12,24 @@
الهاتف
البريد الإلكتروني
تسجيل الدخول عبر Google
+ تسجيل الدخول عبر Google
تسجيل الدخول عبر Facebook
+ تسجيل الدخول عبر Facebook
تسجيل الدخول عبر Twitter
+ تسجيل الدخول عبر Twitter
تسجيل الدخول باستخدام GitHub
+ تسجيل الدخول باستخدام GitHub
تسجيل الدخول عبر البريد الإلكتروني
+ تسجيل الدخول عبر البريد الإلكتروني
تسجيل الدخول عبر رقم الهاتف
+ تسجيل الدخول عبر رقم الهاتف
المتابعة كضيف
تسجيل الدخول باستخدام Apple
+ تسجيل الدخول باستخدام Apple
تسجيل الدخول باستخدام Microsoft
+ تسجيل الدخول باستخدام Microsoft
تسجيل الدخول باستخدام Yahoo
+ تسجيل الدخول باستخدام Yahoo
التالي
البريد الإلكتروني
رقم الهاتف
diff --git a/auth/src/main/res/values-b+es+419/strings.xml b/auth/src/main/res/values-b+es+419/strings.xml
index cb5645e0b..017b893bd 100755
--- a/auth/src/main/res/values-b+es+419/strings.xml
+++ b/auth/src/main/res/values-b+es+419/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-bg/strings.xml b/auth/src/main/res/values-bg/strings.xml
index 12a0dfb60..8b9db9839 100755
--- a/auth/src/main/res/values-bg/strings.xml
+++ b/auth/src/main/res/values-bg/strings.xml
@@ -12,15 +12,24 @@
Телефон
Имейл
Вход с Google
+ Вход с Google
Вход с Facebook
+ Вход с Facebook
Вход с Twitter
+ Вход с Twitter
Вход с GitHub
+ Вход с GitHub
Вход с имейл
+ Вход с имейл
Вход с телефон
+ Вход с телефон
Продължаване като гост
Вход с профил в Apple
+ Вход с профил в Apple
Вход с профил в Microsoft
+ Вход с профил в Microsoft
Вход с профил в Yahoo
+ Вход с профил в Yahoo
Напред
Имейл
Телефонен номер
diff --git a/auth/src/main/res/values-bn/strings.xml b/auth/src/main/res/values-bn/strings.xml
index cf12fa4da..747f19c8c 100755
--- a/auth/src/main/res/values-bn/strings.xml
+++ b/auth/src/main/res/values-bn/strings.xml
@@ -12,15 +12,24 @@
ফোন
ইমেল আইডি
Google দিয়ে সাইন-ইন করুন
+ Google দিয়ে সাইন-ইন করুন
Facebook দিয়ে সাইন-ইন করুন
+ Facebook দিয়ে সাইন-ইন করুন
Twitter দিয়ে সাইন-ইন করুন
+ Twitter দিয়ে সাইন-ইন করুন
GitHub ব্যবহার করে সাইন-ইন করুন
+ GitHub ব্যবহার করে সাইন-ইন করুন
ইমেল দিয়ে সাইন-ইন করুন
+ ইমেল দিয়ে সাইন-ইন করুন
ফোন দিয়ে সাইন-ইন করুন
+ ফোন দিয়ে সাইন-ইন করুন
অতিথি হিসেবে চালিয়ে যান
Apple-এর সাথে সাইন-ইন করুন
+ Apple-এর সাথে সাইন-ইন করুন
Microsoft-এর সাথে সাইন-ইন করুন
+ Microsoft-এর সাথে সাইন-ইন করুন
Yahoo-এর সাথে সাইন-ইন করুন
+ Yahoo-এর সাথে সাইন-ইন করুন
পরবর্তী
ইমেল
ফোন নম্বর
diff --git a/auth/src/main/res/values-ca/strings.xml b/auth/src/main/res/values-ca/strings.xml
index 7579e8fd2..5b56f2489 100755
--- a/auth/src/main/res/values-ca/strings.xml
+++ b/auth/src/main/res/values-ca/strings.xml
@@ -12,15 +12,24 @@
Telèfon
Adreça electrònica
Inicia la sessió amb Google
+ Inicia la sessió amb Google
Inicia la sessió amb Facebook
+ Inicia la sessió amb Facebook
Inicia la sessió amb Twitter
+ Inicia la sessió amb Twitter
Inicia la sessió amb GitHub
+ Inicia la sessió amb GitHub
Inicia la sessió amb l\'adreça electrònica
+ Inicia la sessió amb l\'adreça electrònica
Inicia la sessió amb el telèfon
+ Inicia la sessió amb el telèfon
Continua com a convidat
Inicia la sessió amb Apple
+ Inicia la sessió amb Apple
Inicia la sessió amb Microsoft
+ Inicia la sessió amb Microsoft
Inicia la sessió amb Yahoo
+ Inicia la sessió amb Yahoo
Següent
Adreça electrònica
Número de telèfon
diff --git a/auth/src/main/res/values-cs/strings.xml b/auth/src/main/res/values-cs/strings.xml
index 24416214f..57afae344 100755
--- a/auth/src/main/res/values-cs/strings.xml
+++ b/auth/src/main/res/values-cs/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-mail
Přihlásit se přes Google
+ Přihlásit se přes Google
Přihlásit se přes Facebook
+ Přihlásit se přes Facebook
Přihlásit se přes Twitter
+ Přihlásit se přes Twitter
Přihlásit se přes GitHub
+ Přihlásit se přes GitHub
Přihlásit se pomocí e-mailu
+ Přihlásit se pomocí e-mailu
Přihlásit se pomocí telefonu
+ Přihlásit se pomocí telefonu
Pokračovat jako host
Přihlásit se účtem Apple
+ Přihlásit se účtem Apple
Přihlásit se účtem Microsoft
+ Přihlásit se účtem Microsoft
Přihlásit se účtem Yahoo
+ Přihlásit se účtem Yahoo
Další
E-mail
Telefonní číslo
diff --git a/auth/src/main/res/values-da/strings.xml b/auth/src/main/res/values-da/strings.xml
index ffc351bbd..0b91742aa 100755
--- a/auth/src/main/res/values-da/strings.xml
+++ b/auth/src/main/res/values-da/strings.xml
@@ -12,15 +12,24 @@
Telefon
Mail
Log ind med Google
+ Log ind med Google
Log ind med Facebook
+ Log ind med Facebook
Log ind med Twitter
+ Log ind med Twitter
Log ind med GitHub
+ Log ind med GitHub
Log ind med mail
+ Log ind med mail
Log ind med telefon
+ Log ind med telefon
Fortsæt som gæst
Log ind med Apple
+ Log ind med Apple
Log ind med Microsoft
+ Log ind med Microsoft
Log ind med Yahoo
+ Log ind med Yahoo
Næste
Mail
Telefonnummer
diff --git a/auth/src/main/res/values-de-rAT/strings.xml b/auth/src/main/res/values-de-rAT/strings.xml
index f335dbae7..427f52319 100755
--- a/auth/src/main/res/values-de-rAT/strings.xml
+++ b/auth/src/main/res/values-de-rAT/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-Mail
Über Google anmelden
+ Über Google anmelden
Über Facebook anmelden
+ Über Facebook anmelden
Über Twitter anmelden
+ Über Twitter anmelden
Über GitHub anmelden
+ Über GitHub anmelden
Mit einer E-Mail-Adresse anmelden
+ Mit einer E-Mail-Adresse anmelden
Mit einer Telefonnummer anmelden
+ Mit einer Telefonnummer anmelden
Als Gast fortfahren
Über Apple anmelden
+ Über Apple anmelden
Über Microsoft anmelden
+ Über Microsoft anmelden
Über Yahoo anmelden
+ Über Yahoo anmelden
Weiter
E-Mail-Adresse
Telefonnummer
diff --git a/auth/src/main/res/values-de-rCH/strings.xml b/auth/src/main/res/values-de-rCH/strings.xml
index 1bef81579..316f26f09 100755
--- a/auth/src/main/res/values-de-rCH/strings.xml
+++ b/auth/src/main/res/values-de-rCH/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-Mail
Über Google anmelden
+ Über Google anmelden
Über Facebook anmelden
+ Über Facebook anmelden
Über Twitter anmelden
+ Über Twitter anmelden
Über GitHub anmelden
+ Über GitHub anmelden
Mit einer E-Mail-Adresse anmelden
+ Mit einer E-Mail-Adresse anmelden
Mit einer Telefonnummer anmelden
+ Mit einer Telefonnummer anmelden
Als Gast fortfahren
Über Apple anmelden
+ Über Apple anmelden
Über Microsoft anmelden
+ Über Microsoft anmelden
Über Yahoo anmelden
+ Über Yahoo anmelden
Weiter
E-Mail-Adresse
Telefonnummer
diff --git a/auth/src/main/res/values-de/strings.xml b/auth/src/main/res/values-de/strings.xml
index 5d5d6959b..e7c0a9bd4 100755
--- a/auth/src/main/res/values-de/strings.xml
+++ b/auth/src/main/res/values-de/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-Mail
Über Google anmelden
+ Über Google anmelden
Über Facebook anmelden
+ Über Facebook anmelden
Über Twitter anmelden
+ Über Twitter anmelden
Über GitHub anmelden
+ Über GitHub anmelden
Mit einer E-Mail-Adresse anmelden
+ Mit einer E-Mail-Adresse anmelden
Mit einer Telefonnummer anmelden
+ Mit einer Telefonnummer anmelden
Als Gast fortfahren
Über Apple anmelden
+ Über Apple anmelden
Über Microsoft anmelden
+ Über Microsoft anmelden
Über Yahoo anmelden
+ Über Yahoo anmelden
Weiter
E-Mail-Adresse
Telefonnummer
diff --git a/auth/src/main/res/values-el/strings.xml b/auth/src/main/res/values-el/strings.xml
index b2a60e94a..7a1872d87 100755
--- a/auth/src/main/res/values-el/strings.xml
+++ b/auth/src/main/res/values-el/strings.xml
@@ -12,15 +12,24 @@
Τηλέφωνο
Ηλεκτρονικό ταχυδρομείο
Σύνδεση μέσω Google
+ Σύνδεση μέσω Google
Σύνδεση μέσω Facebook
+ Σύνδεση μέσω Facebook
Σύνδεση μέσω Twitter
+ Σύνδεση μέσω Twitter
Σύνδεση μέσω GitHub
+ Σύνδεση μέσω GitHub
Σύνδεση μέσω ηλεκτρονικού ταχυδρομείου
+ Σύνδεση μέσω ηλεκτρονικού ταχυδρομείου
Σύνδεση μέσω τηλεφώνου
+ Σύνδεση μέσω τηλεφώνου
Συνέχεια ως επισκέπτης
Σύνδεση μέσω Apple
+ Σύνδεση μέσω Apple
Σύνδεση μέσω Microsoft
+ Σύνδεση μέσω Microsoft
Σύνδεση μέσω Yahoo
+ Σύνδεση μέσω Yahoo
Επόμενο
Ηλεκτρονικό ταχυδρομείο
Αριθμός τηλεφώνου
diff --git a/auth/src/main/res/values-en-rAU/strings.xml b/auth/src/main/res/values-en-rAU/strings.xml
index f5fa2e477..33bd778b7 100755
--- a/auth/src/main/res/values-en-rAU/strings.xml
+++ b/auth/src/main/res/values-en-rAU/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rCA/strings.xml b/auth/src/main/res/values-en-rCA/strings.xml
index 38867fd9c..9534d4b43 100755
--- a/auth/src/main/res/values-en-rCA/strings.xml
+++ b/auth/src/main/res/values-en-rCA/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rGB/strings.xml b/auth/src/main/res/values-en-rGB/strings.xml
index 62a4b3d9a..f86d87442 100755
--- a/auth/src/main/res/values-en-rGB/strings.xml
+++ b/auth/src/main/res/values-en-rGB/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rIE/strings.xml b/auth/src/main/res/values-en-rIE/strings.xml
index eb97f5eb1..5d7f7b2f3 100755
--- a/auth/src/main/res/values-en-rIE/strings.xml
+++ b/auth/src/main/res/values-en-rIE/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rIN/strings.xml b/auth/src/main/res/values-en-rIN/strings.xml
index eb97f5eb1..5d7f7b2f3 100755
--- a/auth/src/main/res/values-en-rIN/strings.xml
+++ b/auth/src/main/res/values-en-rIN/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rSG/strings.xml b/auth/src/main/res/values-en-rSG/strings.xml
index eb97f5eb1..5d7f7b2f3 100755
--- a/auth/src/main/res/values-en-rSG/strings.xml
+++ b/auth/src/main/res/values-en-rSG/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-en-rZA/strings.xml b/auth/src/main/res/values-en-rZA/strings.xml
index eb97f5eb1..5d7f7b2f3 100755
--- a/auth/src/main/res/values-en-rZA/strings.xml
+++ b/auth/src/main/res/values-en-rZA/strings.xml
@@ -12,15 +12,24 @@
Phone
Email
Sign in with Google
+ Continue with Google
Sign in with Facebook
+ Continue with Facebook
Sign in with X
+ Continue with X
Sign in with GitHub
+ Continue with GitHub
Sign in with email
+ Continue with email
Sign in with phone
+ Continue with phone
Continue as a guest
Sign in with Apple
+ Continue with Apple
Sign in with Microsoft
+ Continue with Microsoft
Sign in with Yahoo
+ Continue with Yahoo
Next
Email
Phone number
diff --git a/auth/src/main/res/values-es-rAR/strings.xml b/auth/src/main/res/values-es-rAR/strings.xml
index 5a882557c..df6acc012 100755
--- a/auth/src/main/res/values-es-rAR/strings.xml
+++ b/auth/src/main/res/values-es-rAR/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rBO/strings.xml b/auth/src/main/res/values-es-rBO/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rBO/strings.xml
+++ b/auth/src/main/res/values-es-rBO/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rCL/strings.xml b/auth/src/main/res/values-es-rCL/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rCL/strings.xml
+++ b/auth/src/main/res/values-es-rCL/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rCO/strings.xml b/auth/src/main/res/values-es-rCO/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rCO/strings.xml
+++ b/auth/src/main/res/values-es-rCO/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rCR/strings.xml b/auth/src/main/res/values-es-rCR/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rCR/strings.xml
+++ b/auth/src/main/res/values-es-rCR/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rDO/strings.xml b/auth/src/main/res/values-es-rDO/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rDO/strings.xml
+++ b/auth/src/main/res/values-es-rDO/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rEC/strings.xml b/auth/src/main/res/values-es-rEC/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rEC/strings.xml
+++ b/auth/src/main/res/values-es-rEC/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rGT/strings.xml b/auth/src/main/res/values-es-rGT/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rGT/strings.xml
+++ b/auth/src/main/res/values-es-rGT/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rHN/strings.xml b/auth/src/main/res/values-es-rHN/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rHN/strings.xml
+++ b/auth/src/main/res/values-es-rHN/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rMX/strings.xml b/auth/src/main/res/values-es-rMX/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rMX/strings.xml
+++ b/auth/src/main/res/values-es-rMX/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rNI/strings.xml b/auth/src/main/res/values-es-rNI/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rNI/strings.xml
+++ b/auth/src/main/res/values-es-rNI/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rPA/strings.xml b/auth/src/main/res/values-es-rPA/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rPA/strings.xml
+++ b/auth/src/main/res/values-es-rPA/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rPE/strings.xml b/auth/src/main/res/values-es-rPE/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rPE/strings.xml
+++ b/auth/src/main/res/values-es-rPE/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rPR/strings.xml b/auth/src/main/res/values-es-rPR/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rPR/strings.xml
+++ b/auth/src/main/res/values-es-rPR/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rPY/strings.xml b/auth/src/main/res/values-es-rPY/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rPY/strings.xml
+++ b/auth/src/main/res/values-es-rPY/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rSV/strings.xml b/auth/src/main/res/values-es-rSV/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rSV/strings.xml
+++ b/auth/src/main/res/values-es-rSV/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rUS/strings.xml b/auth/src/main/res/values-es-rUS/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rUS/strings.xml
+++ b/auth/src/main/res/values-es-rUS/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rUY/strings.xml b/auth/src/main/res/values-es-rUY/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rUY/strings.xml
+++ b/auth/src/main/res/values-es-rUY/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es-rVE/strings.xml b/auth/src/main/res/values-es-rVE/strings.xml
index 37c0546f1..9ebc9ee65 100755
--- a/auth/src/main/res/values-es-rVE/strings.xml
+++ b/auth/src/main/res/values-es-rVE/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Acceder con Google
+ Acceder con Google
Acceder con Facebook
+ Acceder con Facebook
Acceder con Twitter
+ Acceder con Twitter
Acceder con GitHub
+ Acceder con GitHub
Acceder con el correo electrónico
+ Acceder con el correo electrónico
Acceder con el teléfono
+ Acceder con el teléfono
Continuar como invitado
Acceder con Apple
+ Acceder con Apple
Acceder con Microsoft
+ Acceder con Microsoft
Acceder con Yahoo
+ Acceder con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-es/strings.xml b/auth/src/main/res/values-es/strings.xml
index 49412d50f..5f8895314 100755
--- a/auth/src/main/res/values-es/strings.xml
+++ b/auth/src/main/res/values-es/strings.xml
@@ -12,15 +12,24 @@
Teléfono
Correo electrónico
Iniciar sesión con Google
+ Iniciar sesión con Google
Iniciar sesión con Facebook
+ Iniciar sesión con Facebook
Iniciar sesión con Twitter
+ Iniciar sesión con Twitter
Iniciar sesión con GitHub
+ Iniciar sesión con GitHub
Iniciar sesión con el correo electrónico
+ Iniciar sesión con el correo electrónico
Iniciar sesión con el teléfono
+ Iniciar sesión con el teléfono
Continuar como invitado
Iniciar sesión con Apple
+ Iniciar sesión con Apple
Iniciar sesión con Microsoft
+ Iniciar sesión con Microsoft
Iniciar sesión con Yahoo
+ Iniciar sesión con Yahoo
Siguiente
Correo electrónico
Número de teléfono
diff --git a/auth/src/main/res/values-fa/strings.xml b/auth/src/main/res/values-fa/strings.xml
index 2382d4580..2215781c2 100755
--- a/auth/src/main/res/values-fa/strings.xml
+++ b/auth/src/main/res/values-fa/strings.xml
@@ -12,15 +12,24 @@
تلفن
ایمیل
ورود به سیستم با Google
+ ورود به سیستم با Google
ورود به سیستم با Facebook
+ ورود به سیستم با Facebook
ورود به سیستم با Twitter
+ ورود به سیستم با Twitter
ورود به سیستم با GitHub
+ ورود به سیستم با GitHub
ورود به سیستم با ایمیل
+ ورود به سیستم با ایمیل
ورود به سیستم با تلفن
+ ورود به سیستم با تلفن
ادامه بهعنوان مهمان
ورود به سیستم با Apple
+ ورود به سیستم با Apple
ورود به سیستم با Microsoft
+ ورود به سیستم با Microsoft
ورود به سیستم با Yahoo
+ ورود به سیستم با Yahoo
بعدی
ایمیل
شماره تلفن
diff --git a/auth/src/main/res/values-fi/strings.xml b/auth/src/main/res/values-fi/strings.xml
index cd9294ed9..7c45770a8 100755
--- a/auth/src/main/res/values-fi/strings.xml
+++ b/auth/src/main/res/values-fi/strings.xml
@@ -12,15 +12,24 @@
Puhelin
Sähköposti
Kirjaudu Google-tilillä
+ Kirjaudu Google-tilillä
Kirjaudu Facebook-tilillä
+ Kirjaudu Facebook-tilillä
Kirjaudu Twitter-tilillä
+ Kirjaudu Twitter-tilillä
Kirjaudu GitHub-tilillä
+ Kirjaudu GitHub-tilillä
Kirjaudu sähköpostilla
+ Kirjaudu sähköpostilla
Kirjaudu puhelimella
+ Kirjaudu puhelimella
Jatka vieraana
Kirjaudu sisään Apple-tilillä
+ Kirjaudu sisään Apple-tilillä
Kirjaudu sisään Microsoft-tilillä
+ Kirjaudu sisään Microsoft-tilillä
Kirjaudu sisään Yahoo-tilillä
+ Kirjaudu sisään Yahoo-tilillä
Seuraava
Sähköposti
Puhelinnumero
diff --git a/auth/src/main/res/values-fil/strings.xml b/auth/src/main/res/values-fil/strings.xml
index 29c678526..fc2474b26 100755
--- a/auth/src/main/res/values-fil/strings.xml
+++ b/auth/src/main/res/values-fil/strings.xml
@@ -12,15 +12,24 @@
Telepono
Email
Mag-sign in sa Google
+ Mag-sign in sa Google
Mag-sign in sa Facebook
+ Mag-sign in sa Facebook
Mag-sign in sa Twitter
+ Mag-sign in sa Twitter
Mag-sign in sa GitHub
+ Mag-sign in sa GitHub
Mag-sign in gamit ang email
+ Mag-sign in gamit ang email
Mag-sign in gamit ang telepono
+ Mag-sign in gamit ang telepono
Magpatuloy bilang bisita
Mag-sign in sa Apple
+ Mag-sign in sa Apple
Mag-sign in sa Microsoft
+ Mag-sign in sa Microsoft
Mag-sign in sa Yahoo
+ Mag-sign in sa Yahoo
Susunod
Mag-email
Numero ng Telepono
diff --git a/auth/src/main/res/values-fr-rCH/strings.xml b/auth/src/main/res/values-fr-rCH/strings.xml
index c02f2021b..dd6c99b1b 100755
--- a/auth/src/main/res/values-fr-rCH/strings.xml
+++ b/auth/src/main/res/values-fr-rCH/strings.xml
@@ -12,15 +12,24 @@
Numéro de téléphone
Adresse e-mail
Se connecter avec Google
+ Se connecter avec Google
Se connecter avec Facebook
+ Se connecter avec Facebook
Se connecter avec Twitter
+ Se connecter avec Twitter
Se connecter avec GitHub
+ Se connecter avec GitHub
Se connecter avec une adresse e-mail
+ Se connecter avec une adresse e-mail
Se connecter avec un téléphone
+ Se connecter avec un téléphone
Continuer en tant qu\'invité
Se connecter avec Apple
+ Se connecter avec Apple
Se connecter avec Microsoft
+ Se connecter avec Microsoft
Se connecter avec Yahoo
+ Se connecter avec Yahoo
Suivant
E-mail
Numéro de téléphone
diff --git a/auth/src/main/res/values-fr/strings.xml b/auth/src/main/res/values-fr/strings.xml
index c753053a8..ceaf93d6e 100755
--- a/auth/src/main/res/values-fr/strings.xml
+++ b/auth/src/main/res/values-fr/strings.xml
@@ -12,15 +12,24 @@
Numéro de téléphone
Adresse e-mail
Se connecter avec Google
+ Se connecter avec Google
Se connecter avec Facebook
+ Se connecter avec Facebook
Se connecter avec Twitter
+ Se connecter avec Twitter
Se connecter avec GitHub
+ Se connecter avec GitHub
Se connecter avec une adresse e-mail
+ Se connecter avec une adresse e-mail
Se connecter avec un téléphone
+ Se connecter avec un téléphone
Continuer en tant qu\'invité
Se connecter avec Apple
+ Se connecter avec Apple
Se connecter avec Microsoft
+ Se connecter avec Microsoft
Se connecter avec Yahoo
+ Se connecter avec Yahoo
Suivant
E-mail
Numéro de téléphone
diff --git a/auth/src/main/res/values-gsw/strings.xml b/auth/src/main/res/values-gsw/strings.xml
index 42bd8e23f..2b586c600 100755
--- a/auth/src/main/res/values-gsw/strings.xml
+++ b/auth/src/main/res/values-gsw/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-Mail
Über Google anmelden
+ Über Google anmelden
Über Facebook anmelden
+ Über Facebook anmelden
Über Twitter anmelden
+ Über Twitter anmelden
Über GitHub anmelden
+ Über GitHub anmelden
Mit einer E-Mail-Adresse anmelden
+ Mit einer E-Mail-Adresse anmelden
Mit einer Telefonnummer anmelden
+ Mit einer Telefonnummer anmelden
Als Gast fortfahren
Über Apple anmelden
+ Über Apple anmelden
Über Microsoft anmelden
+ Über Microsoft anmelden
Über Yahoo anmelden
+ Über Yahoo anmelden
Weiter
E-Mail-Adresse
Telefonnummer
diff --git a/auth/src/main/res/values-gu/strings.xml b/auth/src/main/res/values-gu/strings.xml
index 138244ce3..f331a75d3 100755
--- a/auth/src/main/res/values-gu/strings.xml
+++ b/auth/src/main/res/values-gu/strings.xml
@@ -12,15 +12,24 @@
ફોન
ઇમેઇલ
Google વડે સાઇન ઇન કરો
+ Google વડે સાઇન ઇન કરો
Facebook વડે સાઇન ઇન કરો
+ Facebook વડે સાઇન ઇન કરો
Twitter વડે સાઇન ઇન કરો
+ Twitter વડે સાઇન ઇન કરો
GitHub વડે સાઇન ઇન કરો
+ GitHub વડે સાઇન ઇન કરો
ઇમેઇલ વડે સાઇન ઇન કરો
+ ઇમેઇલ વડે સાઇન ઇન કરો
ફોન વડે સાઇન ઇન કરો
+ ફોન વડે સાઇન ઇન કરો
અતિથિ તરીકે ચાલુ રાખો
Apple વડે સાઇન ઇન કરો
+ Apple વડે સાઇન ઇન કરો
Microsoft વડે સાઇન ઇન કરો
+ Microsoft વડે સાઇન ઇન કરો
Yahoo વડે સાઇન ઇન કરો
+ Yahoo વડે સાઇન ઇન કરો
આગળ
ઇમેઇલ
ફોન નંબર
diff --git a/auth/src/main/res/values-hi/strings.xml b/auth/src/main/res/values-hi/strings.xml
index 805bd88d7..847f6c169 100755
--- a/auth/src/main/res/values-hi/strings.xml
+++ b/auth/src/main/res/values-hi/strings.xml
@@ -12,15 +12,24 @@
फ़ोन
ईमेल
Google से प्रवेश करें
+ Google से प्रवेश करें
Facebook से प्रवेश करें
+ Facebook से प्रवेश करें
Twitter से प्रवेश करें
+ Twitter से प्रवेश करें
GitHub के साथ साइन इन करें
+ GitHub के साथ साइन इन करें
ईमेल से प्रवेश करें
+ ईमेल से प्रवेश करें
फ़ोन से प्रवेश करें
+ फ़ोन से प्रवेश करें
मेहमान के रूप में जारी रखें
Apple खाते से साइन इन करें
+ Apple खाते से साइन इन करें
Microsoft खाते से साइन इन करें
+ Microsoft खाते से साइन इन करें
Yahoo खाते से साइन इन करें
+ Yahoo खाते से साइन इन करें
अगला
ईमेल
फ़ोन नंबर
diff --git a/auth/src/main/res/values-hr/strings.xml b/auth/src/main/res/values-hr/strings.xml
index bfdc450ac..4a481d5b3 100755
--- a/auth/src/main/res/values-hr/strings.xml
+++ b/auth/src/main/res/values-hr/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-adresa
Prijava putem Googlea
+ Prijava putem Googlea
Prijava putem Facebooka
+ Prijava putem Facebooka
Prijava putem Twittera
+ Prijava putem Twittera
Prijava putem GitHuba
+ Prijava putem GitHuba
Prijava putem e-adrese
+ Prijava putem e-adrese
Prijava putem telefona
+ Prijava putem telefona
Nastavi kao gost
Prijavite se Apple računom
+ Prijavite se Apple računom
Prijavite se Microsoft računom
+ Prijavite se Microsoft računom
Prijavite se Yahoo računom
+ Prijavite se Yahoo računom
Dalje
E-adresa
Telefonski broj
diff --git a/auth/src/main/res/values-hu/strings.xml b/auth/src/main/res/values-hu/strings.xml
index a9b258bec..6f8f96e28 100755
--- a/auth/src/main/res/values-hu/strings.xml
+++ b/auth/src/main/res/values-hu/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-mail
Bejelentkezés Google-fiókkal
+ Bejelentkezés Google-fiókkal
Bejelentkezés Facebook-fiókkal
+ Bejelentkezés Facebook-fiókkal
Bejelentkezés Twitter-fiókkal
+ Bejelentkezés Twitter-fiókkal
Bejelentkezés GitHubbal
+ Bejelentkezés GitHubbal
Bejelentkezés e-mail-fiókkal
+ Bejelentkezés e-mail-fiókkal
Bejelentkezés telefonnal
+ Bejelentkezés telefonnal
Folytatás vendégként
Bejelentkezés Apple-fiókkal
+ Bejelentkezés Apple-fiókkal
Bejelentkezés Microsoft-fiókkal
+ Bejelentkezés Microsoft-fiókkal
Bejelentkezés Yahoo-fiókkal
+ Bejelentkezés Yahoo-fiókkal
Következő
E-mail
Telefonszám
diff --git a/auth/src/main/res/values-in/strings.xml b/auth/src/main/res/values-in/strings.xml
index 01da423e5..5e125242a 100755
--- a/auth/src/main/res/values-in/strings.xml
+++ b/auth/src/main/res/values-in/strings.xml
@@ -12,15 +12,24 @@
Ponsel
Email
Login dengan Google
+ Login dengan Google
Login dengan Facebook
+ Login dengan Facebook
Login dengan Twitter
+ Login dengan Twitter
Login dengan GitHub
+ Login dengan GitHub
Login dengan email
+ Login dengan email
Login dengan nomor telepon
+ Login dengan nomor telepon
Lanjutkan sebagai tamu
Login dengan Apple
+ Login dengan Apple
Login dengan Microsoft
+ Login dengan Microsoft
Login dengan Yahoo
+ Login dengan Yahoo
Berikutnya
Email
Nomor Telepon
diff --git a/auth/src/main/res/values-it/strings.xml b/auth/src/main/res/values-it/strings.xml
index 6c0c5c6d4..dd6ab26a7 100755
--- a/auth/src/main/res/values-it/strings.xml
+++ b/auth/src/main/res/values-it/strings.xml
@@ -12,15 +12,24 @@
Telefono
Email
Accedi con Google
+ Accedi con Google
Accedi con Facebook
+ Accedi con Facebook
Accedi con Twitter
+ Accedi con Twitter
Accedi con GitHub
+ Accedi con GitHub
Accedi con l\'indirizzo email
+ Accedi con l\'indirizzo email
Accedi con il telefono
+ Accedi con il telefono
Continua come ospite
Accedi con Apple
+ Accedi con Apple
Accedi con Microsoft
+ Accedi con Microsoft
Accedi con Yahoo
+ Accedi con Yahoo
Avanti
Indirizzo email
Numero di telefono
diff --git a/auth/src/main/res/values-iw/strings.xml b/auth/src/main/res/values-iw/strings.xml
index 089494e0e..712bfec7d 100755
--- a/auth/src/main/res/values-iw/strings.xml
+++ b/auth/src/main/res/values-iw/strings.xml
@@ -12,15 +12,24 @@
טלפון
אימייל
כניסה באמצעות Google
+ כניסה באמצעות Google
כניסה באמצעות Facebook
+ כניסה באמצעות Facebook
כניסה באמצעות Twitter
+ כניסה באמצעות Twitter
כניסה באמצעות GitHub
+ כניסה באמצעות GitHub
כניסה באמצעות אימייל
+ כניסה באמצעות אימייל
כניסה באמצעות הטלפון
+ כניסה באמצעות הטלפון
המשך כאורח
כניסה באמצעות Apple
+ כניסה באמצעות Apple
כניסה באמצעות Microsoft
+ כניסה באמצעות Microsoft
כניסה באמצעות Yahoo
+ כניסה באמצעות Yahoo
הבא
אימייל
מספר טלפון
diff --git a/auth/src/main/res/values-ja/strings.xml b/auth/src/main/res/values-ja/strings.xml
index ec4b1f021..dc900e2d8 100755
--- a/auth/src/main/res/values-ja/strings.xml
+++ b/auth/src/main/res/values-ja/strings.xml
@@ -12,15 +12,24 @@
電話
メール
Google でログイン
+ Google でログイン
Facebook でログイン
+ Facebook でログイン
Twitter でログイン
+ Twitter でログイン
GitHub でログイン
+ GitHub でログイン
メールアドレスでログイン
+ メールアドレスでログイン
電話番号でログイン
+ 電話番号でログイン
ゲストとして続行
Apple アカウントでログイン
+ Apple アカウントでログイン
Microsoft アカウントでログイン
+ Microsoft アカウントでログイン
Yahoo アカウントでログイン
+ Yahoo アカウントでログイン
次へ
メールアドレス
電話番号
diff --git a/auth/src/main/res/values-kn/strings.xml b/auth/src/main/res/values-kn/strings.xml
index 1ce94c19a..31f780a6a 100755
--- a/auth/src/main/res/values-kn/strings.xml
+++ b/auth/src/main/res/values-kn/strings.xml
@@ -12,15 +12,24 @@
ಫೋನ್
ಇಮೇಲ್
Google ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Google ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
Facebook ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Facebook ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
Twitter ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Twitter ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
GitHub ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ GitHub ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
ಇಮೇಲ್ ಜೊತೆ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ ಇಮೇಲ್ ಜೊತೆ ಸೈನ್ ಇನ್ ಮಾಡಿ
ಫೋನ್ ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ ಫೋನ್ ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
ಅತಿಥಿಯಂತೆ ಮುಂದುವರಿಸಿ
Apple ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Apple ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
Microsoft ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Microsoft ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
Yahoo ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
+ Yahoo ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಿ
ಮುಂದೆ
ಇಮೇಲ್
ಫೋನ್ ಸಂಖ್ಯೆ
diff --git a/auth/src/main/res/values-ko/strings.xml b/auth/src/main/res/values-ko/strings.xml
index ad5bc3248..eb6e1d22b 100755
--- a/auth/src/main/res/values-ko/strings.xml
+++ b/auth/src/main/res/values-ko/strings.xml
@@ -12,15 +12,24 @@
전화
이메일
Google 계정으로 로그인
+ Google 계정으로 로그인
Facebook으로 로그인
+ Facebook으로 로그인
Twitter로 로그인
+ Twitter로 로그인
GitHub로 로그인
+ GitHub로 로그인
이메일로 로그인
+ 이메일로 로그인
전화로 로그인
+ 전화로 로그인
비회원으로 진행
Apple 계정으로 로그인
+ Apple 계정으로 로그인
Microsoft 계정으로 로그인
+ Microsoft 계정으로 로그인
Yahoo 계정으로 로그인
+ Yahoo 계정으로 로그인
다음
이메일
전화번호
diff --git a/auth/src/main/res/values-ln/strings.xml b/auth/src/main/res/values-ln/strings.xml
index 94cb14751..0ee345992 100755
--- a/auth/src/main/res/values-ln/strings.xml
+++ b/auth/src/main/res/values-ln/strings.xml
@@ -12,15 +12,24 @@
Numéro de téléphone
Adresse e-mail
Se connecter avec Google
+ Se connecter avec Google
Se connecter avec Facebook
+ Se connecter avec Facebook
Se connecter avec Twitter
+ Se connecter avec Twitter
Se connecter avec GitHub
+ Se connecter avec GitHub
Se connecter avec une adresse e-mail
+ Se connecter avec une adresse e-mail
Se connecter avec un téléphone
+ Se connecter avec un téléphone
Continuer en tant qu\'invité
Se connecter avec Apple
+ Se connecter avec Apple
Se connecter avec Microsoft
+ Se connecter avec Microsoft
Se connecter avec Yahoo
+ Se connecter avec Yahoo
Suivant
E-mail
Numéro de téléphone
diff --git a/auth/src/main/res/values-lt/strings.xml b/auth/src/main/res/values-lt/strings.xml
index 979e9cc8d..cef70d317 100755
--- a/auth/src/main/res/values-lt/strings.xml
+++ b/auth/src/main/res/values-lt/strings.xml
@@ -12,15 +12,24 @@
Telefono numeris
El. pašto
Prisijungti per „Google“
+ Prisijungti per „Google“
Prisijungti per „Facebook“
+ Prisijungti per „Facebook“
Prisijungti per „Twitter“
+ Prisijungti per „Twitter“
Prisijungti per „GitHub“
+ Prisijungti per „GitHub“
Prisijungti nurodant el. pašto adresą
+ Prisijungti nurodant el. pašto adresą
Prisijungti nurodant telefono numerį
+ Prisijungti nurodant telefono numerį
Tęsti kaip svečiui
Prisijungti per „Apple“
+ Prisijungti per „Apple“
Prisijungti per „Microsoft“
+ Prisijungti per „Microsoft“
Prisijungti per „Yahoo“
+ Prisijungti per „Yahoo“
Kitas
El. pašto adresas
Telefono numeris
diff --git a/auth/src/main/res/values-lv/strings.xml b/auth/src/main/res/values-lv/strings.xml
index b7c1de4a6..fbba1e17a 100755
--- a/auth/src/main/res/values-lv/strings.xml
+++ b/auth/src/main/res/values-lv/strings.xml
@@ -12,15 +12,24 @@
Tālrunis
E-pasts
Pierakstīties ar Google
+ Pierakstīties ar Google
Pierakstīties ar Facebook
+ Pierakstīties ar Facebook
Pierakstīties ar Twitter
+ Pierakstīties ar Twitter
Pierakstīties, izmantojot GitHub
+ Pierakstīties, izmantojot GitHub
Pierakstīties ar e-pasta adresi
+ Pierakstīties ar e-pasta adresi
Pierakstīties ar tālruni
+ Pierakstīties ar tālruni
Turpināt kā viesim
Pierakstīties ar Apple kontu
+ Pierakstīties ar Apple kontu
Pierakstīties ar Microsoft kontu
+ Pierakstīties ar Microsoft kontu
Pierakstīties ar Yahoo kontu
+ Pierakstīties ar Yahoo kontu
Tālāk
E-pasts
Tālruņa numurs
diff --git a/auth/src/main/res/values-mo/strings.xml b/auth/src/main/res/values-mo/strings.xml
index 46c2fac46..73ac609a4 100755
--- a/auth/src/main/res/values-mo/strings.xml
+++ b/auth/src/main/res/values-mo/strings.xml
@@ -12,15 +12,24 @@
Număr de telefon
Adresă de e-mail
Conectați-vă cu Google
+ Conectați-vă cu Google
Conectați-vă cu Facebook
+ Conectați-vă cu Facebook
Conectați-vă cu Twitter
+ Conectați-vă cu Twitter
Conectați-vă cu GitHub
+ Conectați-vă cu GitHub
Conectați-vă cu adresa de e-mail
+ Conectați-vă cu adresa de e-mail
Conectați-vă cu numărul de telefon
+ Conectați-vă cu numărul de telefon
Continuați ca invitat
Conectați-vă cu Apple
+ Conectați-vă cu Apple
Conectați-vă cu Microsoft
+ Conectați-vă cu Microsoft
Conectați-vă cu Yahoo
+ Conectați-vă cu Yahoo
Înainte
Adresă de e-mail
Număr de telefon
diff --git a/auth/src/main/res/values-mr/strings.xml b/auth/src/main/res/values-mr/strings.xml
index 34bd8d832..00b6ee391 100755
--- a/auth/src/main/res/values-mr/strings.xml
+++ b/auth/src/main/res/values-mr/strings.xml
@@ -12,15 +12,24 @@
फोन
ईमेल
Googleने साइन इन करा
+ Googleने साइन इन करा
Facebookने साइन इन करा
+ Facebookने साइन इन करा
Twitterने साइन इन करा
+ Twitterने साइन इन करा
GitHub सह साइन इन करा
+ GitHub सह साइन इन करा
ईमेलने साइन इन करा
+ ईमेलने साइन इन करा
फोनने साइन इन करा
+ फोनने साइन इन करा
अतिथी म्हणून सुरू ठेवा
Apple वरून साइन इन करा
+ Apple वरून साइन इन करा
Microsoft वरून साइन इन करा
+ Microsoft वरून साइन इन करा
Yahoo वरून साइन इन करा
+ Yahoo वरून साइन इन करा
पुढील
ईमेल
फोन नंबर
diff --git a/auth/src/main/res/values-ms/strings.xml b/auth/src/main/res/values-ms/strings.xml
index 9f0e6b074..be8b30ccc 100755
--- a/auth/src/main/res/values-ms/strings.xml
+++ b/auth/src/main/res/values-ms/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-mel
Log masuk dengan Google
+ Log masuk dengan Google
Log masuk dengan Facebook
+ Log masuk dengan Facebook
Log masuk dengan Twitter
+ Log masuk dengan Twitter
Log masuk dengan GitHub
+ Log masuk dengan GitHub
Log masuk dengan e-mel
+ Log masuk dengan e-mel
Log masuk dengan telefon
+ Log masuk dengan telefon
Teruskan sebagai tetamu
Log masuk dengan Apple
+ Log masuk dengan Apple
Log masuk dengan Microsoft
+ Log masuk dengan Microsoft
Log masuk dengan Yahoo
+ Log masuk dengan Yahoo
Seterusnya
E-mel
Nombor Telefon
diff --git a/auth/src/main/res/values-nb/strings.xml b/auth/src/main/res/values-nb/strings.xml
index f1c3486e0..5cc9c83ac 100755
--- a/auth/src/main/res/values-nb/strings.xml
+++ b/auth/src/main/res/values-nb/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-post
Logg på med Google
+ Logg på med Google
Logg på med Facebook
+ Logg på med Facebook
Logg på med Twitter
+ Logg på med Twitter
Logg på med GitHub
+ Logg på med GitHub
Logg på med e-postadresse
+ Logg på med e-postadresse
Logg på med telefonnummeret ditt
+ Logg på med telefonnummeret ditt
Fortsett som gjest
Logg på med Apple-ID-en din
+ Logg på med Apple-ID-en din
Logg på med Microsoft-kontoen din
+ Logg på med Microsoft-kontoen din
Logg på med Yahoo-kontoen din
+ Logg på med Yahoo-kontoen din
Neste
E-post
Telefonnummer
diff --git a/auth/src/main/res/values-nl/strings.xml b/auth/src/main/res/values-nl/strings.xml
index 2eb2b6b5a..8d011cc4f 100755
--- a/auth/src/main/res/values-nl/strings.xml
+++ b/auth/src/main/res/values-nl/strings.xml
@@ -12,15 +12,24 @@
Telefoon
E-mailadres
Inloggen met Google
+ Inloggen met Google
Inloggen met Facebook
+ Inloggen met Facebook
Inloggen met Twitter
+ Inloggen met Twitter
Inloggen met GitHub
+ Inloggen met GitHub
Inloggen met e-mailadres
+ Inloggen met e-mailadres
Inloggen met telefoon
+ Inloggen met telefoon
Doorgaan als gast
Inloggen met Apple
+ Inloggen met Apple
Inloggen met Microsoft
+ Inloggen met Microsoft
Inloggen met Yahoo
+ Inloggen met Yahoo
Volgende
E-mail
Telefoonnummer
diff --git a/auth/src/main/res/values-no/strings.xml b/auth/src/main/res/values-no/strings.xml
index 04a8c51cd..6afe11726 100755
--- a/auth/src/main/res/values-no/strings.xml
+++ b/auth/src/main/res/values-no/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-post
Logg på med Google
+ Logg på med Google
Logg på med Facebook
+ Logg på med Facebook
Logg på med Twitter
+ Logg på med Twitter
Logg på med GitHub
+ Logg på med GitHub
Logg på med e-postadresse
+ Logg på med e-postadresse
Logg på med telefonnummeret ditt
+ Logg på med telefonnummeret ditt
Fortsett som gjest
Logg på med Apple-ID-en din
+ Logg på med Apple-ID-en din
Logg på med Microsoft-kontoen din
+ Logg på med Microsoft-kontoen din
Logg på med Yahoo-kontoen din
+ Logg på med Yahoo-kontoen din
Neste
E-post
Telefonnummer
diff --git a/auth/src/main/res/values-pl/strings.xml b/auth/src/main/res/values-pl/strings.xml
index 173fceb0b..7fd45ad4e 100755
--- a/auth/src/main/res/values-pl/strings.xml
+++ b/auth/src/main/res/values-pl/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-mail
Zaloguj się przez Google
+ Zaloguj się przez Google
Zaloguj się przez Facebooka
+ Zaloguj się przez Facebooka
Zaloguj się przez Twittera
+ Zaloguj się przez Twittera
Zaloguj się przez GitHuba
+ Zaloguj się przez GitHuba
Zaloguj się za pomocą e-maila
+ Zaloguj się za pomocą e-maila
Zaloguj się z użyciem numeru telefonu
+ Zaloguj się z użyciem numeru telefonu
Kontynuuj jako gość
Zaloguj się przez Apple
+ Zaloguj się przez Apple
Zaloguj się przez Microsoft
+ Zaloguj się przez Microsoft
Zaloguj się przez Yahoo
+ Zaloguj się przez Yahoo
Dalej
Adres e-mail
Numer telefonu
diff --git a/auth/src/main/res/values-pt-rBR/strings.xml b/auth/src/main/res/values-pt-rBR/strings.xml
index ca0029dbe..7e805ba3e 100755
--- a/auth/src/main/res/values-pt-rBR/strings.xml
+++ b/auth/src/main/res/values-pt-rBR/strings.xml
@@ -12,15 +12,24 @@
Telefone
E-mail
Fazer login com o Google
+ Fazer login com o Google
Fazer login com o Facebook
+ Fazer login com o Facebook
Fazer login com o Twitter
+ Fazer login com o Twitter
Fazer login com o GitHub
+ Fazer login com o GitHub
Fazer login com o e-mail
+ Fazer login com o e-mail
Fazer login com o telefone
+ Fazer login com o telefone
Continuar como convidado
Fazer login com a Apple
+ Fazer login com a Apple
Fazer login com a Microsoft
+ Fazer login com a Microsoft
Fazer login com o Yahoo
+ Fazer login com o Yahoo
Próxima
E-mail
Número de telefone
diff --git a/auth/src/main/res/values-pt-rPT/strings.xml b/auth/src/main/res/values-pt-rPT/strings.xml
index 1b5a66801..3844841bc 100755
--- a/auth/src/main/res/values-pt-rPT/strings.xml
+++ b/auth/src/main/res/values-pt-rPT/strings.xml
@@ -12,15 +12,24 @@
Telemóvel
Email
Iniciar sessão com o Google
+ Iniciar sessão com o Google
Iniciar sessão com o Facebook
+ Iniciar sessão com o Facebook
Iniciar sessão com o Twitter
+ Iniciar sessão com o Twitter
Iniciar sessão com o GitHub
+ Iniciar sessão com o GitHub
Iniciar sessão com o email
+ Iniciar sessão com o email
Iniciar sessão com o telemóvel
+ Iniciar sessão com o telemóvel
Continuar como convidado
Iniciar sessão com a Apple
+ Iniciar sessão com a Apple
Iniciar sessão com a Microsoft
+ Iniciar sessão com a Microsoft
Iniciar sessão com o Yahoo
+ Iniciar sessão com o Yahoo
Seguinte
Email
Número de telefone
diff --git a/auth/src/main/res/values-pt/strings.xml b/auth/src/main/res/values-pt/strings.xml
index f101f629a..54473de13 100755
--- a/auth/src/main/res/values-pt/strings.xml
+++ b/auth/src/main/res/values-pt/strings.xml
@@ -12,15 +12,24 @@
Telefone
E-mail
Fazer login com o Google
+ Fazer login com o Google
Fazer login com o Facebook
+ Fazer login com o Facebook
Fazer login com o Twitter
+ Fazer login com o Twitter
Fazer login com o GitHub
+ Fazer login com o GitHub
Fazer login com o e-mail
+ Fazer login com o e-mail
Fazer login com o telefone
+ Fazer login com o telefone
Continuar como convidado
Fazer login com a Apple
+ Fazer login com a Apple
Fazer login com a Microsoft
+ Fazer login com a Microsoft
Fazer login com o Yahoo
+ Fazer login com o Yahoo
Próxima
E-mail
Número de telefone
diff --git a/auth/src/main/res/values-ro/strings.xml b/auth/src/main/res/values-ro/strings.xml
index 9d3afcf6f..c91b2199c 100755
--- a/auth/src/main/res/values-ro/strings.xml
+++ b/auth/src/main/res/values-ro/strings.xml
@@ -12,15 +12,24 @@
Număr de telefon
Adresă de e-mail
Conectați-vă cu Google
+ Conectați-vă cu Google
Conectați-vă cu Facebook
+ Conectați-vă cu Facebook
Conectați-vă cu Twitter
+ Conectați-vă cu Twitter
Conectați-vă cu GitHub
+ Conectați-vă cu GitHub
Conectați-vă cu adresa de e-mail
+ Conectați-vă cu adresa de e-mail
Conectați-vă cu numărul de telefon
+ Conectați-vă cu numărul de telefon
Continuați ca invitat
Conectați-vă cu Apple
+ Conectați-vă cu Apple
Conectați-vă cu Microsoft
+ Conectați-vă cu Microsoft
Conectați-vă cu Yahoo
+ Conectați-vă cu Yahoo
Înainte
Adresă de e-mail
Număr de telefon
diff --git a/auth/src/main/res/values-ru/strings.xml b/auth/src/main/res/values-ru/strings.xml
index 53c4fc56b..f41c29527 100755
--- a/auth/src/main/res/values-ru/strings.xml
+++ b/auth/src/main/res/values-ru/strings.xml
@@ -12,15 +12,24 @@
Телефон
Адрес электронной почты
Войти через аккаунт Google
+ Войти через аккаунт Google
Войти через аккаунт Facebook
+ Войти через аккаунт Facebook
Войти через аккаунт Twitter
+ Войти через аккаунт Twitter
Войти через аккаунт GitHub
+ Войти через аккаунт GitHub
Войти по адресу электронной почты
+ Войти по адресу электронной почты
Войти по номеру телефона
+ Войти по номеру телефона
Продолжить как гость
Войти через аккаунт Apple
+ Войти через аккаунт Apple
Войти через аккаунт Microsoft
+ Войти через аккаунт Microsoft
Войти через аккаунт Yahoo
+ Войти через аккаунт Yahoo
Далее
Адрес электронной почты
Номер телефона
diff --git a/auth/src/main/res/values-sk/strings.xml b/auth/src/main/res/values-sk/strings.xml
index 74126c1e7..2643ea9d2 100755
--- a/auth/src/main/res/values-sk/strings.xml
+++ b/auth/src/main/res/values-sk/strings.xml
@@ -12,15 +12,24 @@
Telefón
E‑mail
Prihlásiť sa účtom Google
+ Prihlásiť sa účtom Google
Prihlásiť sa cez Facebook
+ Prihlásiť sa cez Facebook
Prihlásiť sa cez Twitter
+ Prihlásiť sa cez Twitter
Prihlásiť sa cez GitHub
+ Prihlásiť sa cez GitHub
Prihlásiť sa e-mailom
+ Prihlásiť sa e-mailom
Prihlásiť sa telefónom
+ Prihlásiť sa telefónom
Pokračovať ako hosť
Prihlásiť sa cez Apple
+ Prihlásiť sa cez Apple
Prihlásiť sa cez Microsoft
+ Prihlásiť sa cez Microsoft
Prihlásiť sa cez Yahoo
+ Prihlásiť sa cez Yahoo
Ďalej
E-mail
Telefónne číslo
diff --git a/auth/src/main/res/values-sl/strings.xml b/auth/src/main/res/values-sl/strings.xml
index e6001c924..12c7fee84 100755
--- a/auth/src/main/res/values-sl/strings.xml
+++ b/auth/src/main/res/values-sl/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-pošta
Prijava z Googlom
+ Prijava z Googlom
Prijava z računom za Facebook
+ Prijava z računom za Facebook
Prijava z računom za Twitter
+ Prijava z računom za Twitter
Prijava z računom za GitHub
+ Prijava z računom za GitHub
Prijava z e-poštnim naslovom
+ Prijava z e-poštnim naslovom
Prijava s telefonom
+ Prijava s telefonom
Nadaljuj kot gost
Prijava z računom za Apple
+ Prijava z računom za Apple
Prijava z računom za Microsoft
+ Prijava z računom za Microsoft
Prijava z računom za Yahoo
+ Prijava z računom za Yahoo
Naprej
E-pošta
Telefonska številka
diff --git a/auth/src/main/res/values-sr/strings.xml b/auth/src/main/res/values-sr/strings.xml
index 63c3c21c9..666d446ad 100755
--- a/auth/src/main/res/values-sr/strings.xml
+++ b/auth/src/main/res/values-sr/strings.xml
@@ -12,15 +12,24 @@
Телефон
Имејл
Пријави ме помоћу Google-а
+ Пријави ме помоћу Google-а
Пријави ме помоћу Facebook-а
+ Пријави ме помоћу Facebook-а
Пријави ме помоћу Twitter-а
+ Пријави ме помоћу Twitter-а
Пријавите се помоћу GitHub-а
+ Пријавите се помоћу GitHub-а
Пријави ме помоћу имејла
+ Пријави ме помоћу имејла
Пријави ме помоћу телефона
+ Пријави ме помоћу телефона
Наставите као гост
Пријавите се преко Apple налога
+ Пријавите се преко Apple налога
Пријавите се преко Microsoft налога
+ Пријавите се преко Microsoft налога
Пријавите се преко Yahoo налога
+ Пријавите се преко Yahoo налога
Даље
Имејл
Број телефона
diff --git a/auth/src/main/res/values-sv/strings.xml b/auth/src/main/res/values-sv/strings.xml
index 8d45ca544..79c33bf9d 100755
--- a/auth/src/main/res/values-sv/strings.xml
+++ b/auth/src/main/res/values-sv/strings.xml
@@ -12,15 +12,24 @@
Mobil
E-post
Logga in med Google
+ Logga in med Google
Logga in med Facebook
+ Logga in med Facebook
Logga in med Twitter
+ Logga in med Twitter
Logga in med GitHub
+ Logga in med GitHub
Logga in med e-post
+ Logga in med e-post
Logga in med telefon
+ Logga in med telefon
Fortsätt som gäst
Logga in med Apple
+ Logga in med Apple
Logga in med Microsoft
+ Logga in med Microsoft
Logga in med Yahoo
+ Logga in med Yahoo
Nästa
E-post
Telefonnummer
diff --git a/auth/src/main/res/values-ta/strings.xml b/auth/src/main/res/values-ta/strings.xml
index f41eb7176..a01ca8e42 100755
--- a/auth/src/main/res/values-ta/strings.xml
+++ b/auth/src/main/res/values-ta/strings.xml
@@ -12,15 +12,24 @@
ஃபோன்
மின்னஞ்சல்
Google மூலம் உள்நுழைக
+ Google மூலம் உள்நுழைக
Facebook மூலம் உள்நுழைக
+ Facebook மூலம் உள்நுழைக
Twitter மூலம் உள்நுழைக
+ Twitter மூலம் உள்நுழைக
GitHub பயன்படுத்தி உள்நுழைக
+ GitHub பயன்படுத்தி உள்நுழைக
மின்னஞ்சல் மூலம் உள்நுழைக
+ மின்னஞ்சல் மூலம் உள்நுழைக
ஃபோன் எண் மூலம் உள்நுழைக
+ ஃபோன் எண் மூலம் உள்நுழைக
கெஸ்டாகத் தொடர்க
Apple மூலம் உள்நுழை
+ Apple மூலம் உள்நுழை
Microsoft மூலம் உள்நுழை
+ Microsoft மூலம் உள்நுழை
Yahoo மூலம் உள்நுழை
+ Yahoo மூலம் உள்நுழை
அடுத்து
மின்னஞ்சல்
ஃபோன் எண்
diff --git a/auth/src/main/res/values-th/strings.xml b/auth/src/main/res/values-th/strings.xml
index 248e64533..9dc12174b 100755
--- a/auth/src/main/res/values-th/strings.xml
+++ b/auth/src/main/res/values-th/strings.xml
@@ -12,15 +12,24 @@
โทรศัพท์
อีเมล
ลงชื่อเข้าใช้ด้วย Google
+ ลงชื่อเข้าใช้ด้วย Google
ลงชื่อเข้าใช้ด้วย Facebook
+ ลงชื่อเข้าใช้ด้วย Facebook
ลงชื่อเข้าใช้ด้วย Twitter
+ ลงชื่อเข้าใช้ด้วย Twitter
ลงชื่อเข้าใช้ด้วย GitHub
+ ลงชื่อเข้าใช้ด้วย GitHub
ลงชื่อเข้าใช้ด้วยอีเมล
+ ลงชื่อเข้าใช้ด้วยอีเมล
ลงชื่อเข้าใช้ด้วยโทรศัพท์
+ ลงชื่อเข้าใช้ด้วยโทรศัพท์
ดำเนินการต่อในฐานะผู้เข้าร่วม
ลงชื่อเข้าใช้ด้วย Apple
+ ลงชื่อเข้าใช้ด้วย Apple
ลงชื่อเข้าใช้ด้วย Microsoft
+ ลงชื่อเข้าใช้ด้วย Microsoft
ลงชื่อเข้าใช้ด้วย Yahoo
+ ลงชื่อเข้าใช้ด้วย Yahoo
ถัดไป
อีเมล
หมายเลขโทรศัพท์
diff --git a/auth/src/main/res/values-tl/strings.xml b/auth/src/main/res/values-tl/strings.xml
index e9810f666..bcafcca5e 100755
--- a/auth/src/main/res/values-tl/strings.xml
+++ b/auth/src/main/res/values-tl/strings.xml
@@ -12,15 +12,24 @@
Telepono
Email
Mag-sign in sa Google
+ Mag-sign in sa Google
Mag-sign in sa Facebook
+ Mag-sign in sa Facebook
Mag-sign in sa Twitter
+ Mag-sign in sa Twitter
Mag-sign in sa GitHub
+ Mag-sign in sa GitHub
Mag-sign in gamit ang email
+ Mag-sign in gamit ang email
Mag-sign in gamit ang telepono
+ Mag-sign in gamit ang telepono
Magpatuloy bilang bisita
Mag-sign in sa Apple
+ Mag-sign in sa Apple
Mag-sign in sa Microsoft
+ Mag-sign in sa Microsoft
Mag-sign in sa Yahoo
+ Mag-sign in sa Yahoo
Susunod
Mag-email
Numero ng Telepono
diff --git a/auth/src/main/res/values-tr/strings.xml b/auth/src/main/res/values-tr/strings.xml
index 65614a56c..59b2ba45b 100755
--- a/auth/src/main/res/values-tr/strings.xml
+++ b/auth/src/main/res/values-tr/strings.xml
@@ -12,15 +12,24 @@
Telefon
E-posta
Google ile oturum aç
+ Google ile oturum aç
Facebook ile oturum aç
+ Facebook ile oturum aç
Twitter ile oturum aç
+ Twitter ile oturum aç
GitHub ile oturum aç
+ GitHub ile oturum aç
E-posta ile oturum aç
+ E-posta ile oturum aç
Telefonla oturum aç
+ Telefonla oturum aç
Misafir olarak devam et
Apple ile oturum aç
+ Apple ile oturum aç
Microsoft ile oturum aç
+ Microsoft ile oturum aç
Yahoo ile oturum aç
+ Yahoo ile oturum aç
İleri
E-posta
Telefon Numarası
diff --git a/auth/src/main/res/values-uk/strings.xml b/auth/src/main/res/values-uk/strings.xml
index 9774eb323..2361de7c4 100755
--- a/auth/src/main/res/values-uk/strings.xml
+++ b/auth/src/main/res/values-uk/strings.xml
@@ -12,15 +12,24 @@
Телефон
Електронна пошта
Увійти через Google
+ Увійти через Google
Увійти через Facebook
+ Увійти через Facebook
Увійти через Twitter
+ Увійти через Twitter
Увійти через GitHub
+ Увійти через GitHub
Увійти, використовуючи електронну адресу
+ Увійти, використовуючи електронну адресу
Увійти, використовуючи телефон
+ Увійти, використовуючи телефон
Продовжити як гість
Увійти через обліковий запис Apple
+ Увійти через обліковий запис Apple
Увійти через обліковий запис Microsoft
+ Увійти через обліковий запис Microsoft
Увійти через обліковий запис Yahoo
+ Увійти через обліковий запис Yahoo
Далі
Електронна адреса
Номер телефону
diff --git a/auth/src/main/res/values-ur/strings.xml b/auth/src/main/res/values-ur/strings.xml
index ca8d277dc..9e4268e6c 100755
--- a/auth/src/main/res/values-ur/strings.xml
+++ b/auth/src/main/res/values-ur/strings.xml
@@ -12,15 +12,24 @@
فون
ای میل
Google کے ساتھ سائن ان کریں
+ Google کے ساتھ سائن ان کریں
Facebook کے ساتھ سائن ان کریں
+ Facebook کے ساتھ سائن ان کریں
Twitter کے ساتھ سائن ان کریں
+ Twitter کے ساتھ سائن ان کریں
GitHub کے ساتھ سائن ان کریں
+ GitHub کے ساتھ سائن ان کریں
ای میل کے ساتھ سائن ان کریں
+ ای میل کے ساتھ سائن ان کریں
فون کے ساتھ سائں ان کریں
+ فون کے ساتھ سائں ان کریں
مہمان کے طور پر جاری رکھیں
Apple کے ساتھ سائن ان کریں
+ Apple کے ساتھ سائن ان کریں
Microsoft کے ساتھ سائن ان کریں
+ Microsoft کے ساتھ سائن ان کریں
Yahoo کے ساتھ سائن ان کریں
+ Yahoo کے ساتھ سائن ان کریں
اگلا
ای میل
فون نمبر
diff --git a/auth/src/main/res/values-vi/strings.xml b/auth/src/main/res/values-vi/strings.xml
index b77158b5d..f67c4b703 100755
--- a/auth/src/main/res/values-vi/strings.xml
+++ b/auth/src/main/res/values-vi/strings.xml
@@ -12,15 +12,24 @@
Điện thoại
Email
Đăng nhập bằng Google
+ Đăng nhập bằng Google
Đăng nhập bằng Facebook
+ Đăng nhập bằng Facebook
Đăng nhập bằng Twitter
+ Đăng nhập bằng Twitter
Đăng nhập bằng GitHub
+ Đăng nhập bằng GitHub
Đăng nhập bằng email
+ Đăng nhập bằng email
Đăng nhập bằng điện thoại
+ Đăng nhập bằng điện thoại
Tiếp tục với vai trò người dùng khách
Đăng nhập bằng Apple
+ Đăng nhập bằng Apple
Đăng nhập bằng Microsoft
+ Đăng nhập bằng Microsoft
Đăng nhập bằng Yahoo
+ Đăng nhập bằng Yahoo
Tiếp
Email
Số điện thoại
diff --git a/auth/src/main/res/values-zh-rCN/strings.xml b/auth/src/main/res/values-zh-rCN/strings.xml
index 6fef868ca..64f584490 100755
--- a/auth/src/main/res/values-zh-rCN/strings.xml
+++ b/auth/src/main/res/values-zh-rCN/strings.xml
@@ -12,15 +12,24 @@
电话
电子邮件
使用 Google 帐号登录
+ 使用 Google 帐号登录
使用 Facebook 帐号登录
+ 使用 Facebook 帐号登录
使用 Twitter 帐号登录
+ 使用 Twitter 帐号登录
使用 GitHub 帐号登录
+ 使用 GitHub 帐号登录
使用电子邮件地址登录
+ 使用电子邮件地址登录
使用电话号码登录
+ 使用电话号码登录
以访客身份继续
使用 Apple 帐号登录
+ 使用 Apple 帐号登录
使用 Microsoft 帐号登录
+ 使用 Microsoft 帐号登录
使用 Yahoo 帐号登录
+ 使用 Yahoo 帐号登录
继续
电子邮件地址
电话号码
diff --git a/auth/src/main/res/values-zh-rHK/strings.xml b/auth/src/main/res/values-zh-rHK/strings.xml
index 34a72a156..a2b6f9530 100755
--- a/auth/src/main/res/values-zh-rHK/strings.xml
+++ b/auth/src/main/res/values-zh-rHK/strings.xml
@@ -12,15 +12,24 @@
電話
電子郵件
使用 Google 帳戶登入
+ 使用 Google 帳戶登入
使用 Facebook 帳戶登入
+ 使用 Facebook 帳戶登入
使用 Twitter 帳戶登入
+ 使用 Twitter 帳戶登入
使用 GitHub 登入
+ 使用 GitHub 登入
使用電子郵件地址登入
+ 使用電子郵件地址登入
使用電話號碼登入
+ 使用電話號碼登入
繼續以訪客身分使用
使用 Apple 帳戶登入
+ 使用 Apple 帳戶登入
使用 Microsoft 帳戶登入
+ 使用 Microsoft 帳戶登入
使用 Yahoo 帳戶登入
+ 使用 Yahoo 帳戶登入
繼續
電子郵件
電話號碼
diff --git a/auth/src/main/res/values-zh-rTW/strings.xml b/auth/src/main/res/values-zh-rTW/strings.xml
index e890b3915..97939b4c9 100755
--- a/auth/src/main/res/values-zh-rTW/strings.xml
+++ b/auth/src/main/res/values-zh-rTW/strings.xml
@@ -12,15 +12,24 @@
電話
電子郵件
使用 Google 帳戶登入
+ 使用 Google 帳戶登入
使用 Facebook 帳戶登入
+ 使用 Facebook 帳戶登入
使用 Twitter 帳戶登入
+ 使用 Twitter 帳戶登入
使用 GitHub 登入
+ 使用 GitHub 登入
使用電子郵件地址登入
+ 使用電子郵件地址登入
使用電話號碼登入
+ 使用電話號碼登入
繼續以訪客身分使用
使用 Apple 帳戶登入
+ 使用 Apple 帳戶登入
使用 Microsoft 帳戶登入
+ 使用 Microsoft 帳戶登入
使用 Yahoo 帳戶登入
+ 使用 Yahoo 帳戶登入
繼續
電子郵件
電話號碼
diff --git a/auth/src/main/res/values-zh/strings.xml b/auth/src/main/res/values-zh/strings.xml
index 8e1db852e..a845558c9 100755
--- a/auth/src/main/res/values-zh/strings.xml
+++ b/auth/src/main/res/values-zh/strings.xml
@@ -12,15 +12,24 @@
电话
电子邮件
使用 Google 帐号登录
+ 使用 Google 帐号登录
使用 Facebook 帐号登录
+ 使用 Facebook 帐号登录
使用 Twitter 帐号登录
+ 使用 Twitter 帐号登录
使用 GitHub 帐号登录
+ 使用 GitHub 帐号登录
使用电子邮件地址登录
+ 使用电子邮件地址登录
使用电话号码登录
+ 使用电话号码登录
以访客身份继续
使用 Apple 帐号登录
+ 使用 Apple 帐号登录
使用 Microsoft 帐号登录
+ 使用 Microsoft 帐号登录
使用 Yahoo 帐号登录
+ 使用 Yahoo 帐号登录
继续
电子邮件地址
电话号码
diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml
index 4d0018336..ee88a98a5 100644
--- a/auth/src/main/res/values/strings.xml
+++ b/auth/src/main/res/values/strings.xml
@@ -30,6 +30,15 @@
Sign in with Apple
Sign in with Microsoft
Sign in with Yahoo
+ Continue with Google
+ Continue with Facebook
+ Continue with X
+ Continue with GitHub
+ Continue with email
+ Continue with phone
+ Continue with Apple
+ Continue with Microsoft
+ Continue with Yahoo
Signed in as %1$s
diff --git a/auth/src/test/java/com/firebase/ui/auth/FirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/FirebaseAuthUITest.kt
index b6561315d..05f61c538 100644
--- a/auth/src/test/java/com/firebase/ui/auth/FirebaseAuthUITest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/FirebaseAuthUITest.kt
@@ -18,6 +18,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.test.core.app.ApplicationProvider
+import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
import com.google.android.gms.tasks.TaskCompletionSource
import com.google.common.truth.Truth.assertThat
import com.google.firebase.FirebaseApp
@@ -26,6 +27,7 @@ import com.google.firebase.FirebaseOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException
import com.google.firebase.auth.FirebaseUser
+import com.google.firebase.auth.UserInfo
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -401,6 +403,156 @@ class FirebaseAuthUITest {
}
}
+ @Test
+ fun `signOut() calls Google sign out when user provider is Google`() = runTest {
+ // Setup mock user with Google provider
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockUserInfo = mock(UserInfo::class.java)
+ `when`(mockUserInfo.providerId).thenReturn("google.com")
+ `when`(mockUser.providerId).thenReturn("google.com")
+ `when`(mockUser.providerData).thenReturn(listOf(mockUserInfo))
+
+ // Setup mock auth
+ val mockAuth = mock(FirebaseAuth::class.java)
+ `when`(mockAuth.currentUser).thenReturn(mockUser)
+ doNothing().`when`(mockAuth).signOut()
+
+ // Create mock credential manager provider
+ var googleSignOutCalled = false
+ val mockCredentialManagerProvider = object : AuthProvider.Google.CredentialManagerProvider {
+ override suspend fun getGoogleCredential(
+ context: Context,
+ credentialManager: androidx.credentials.CredentialManager,
+ serverClientId: String,
+ filterByAuthorizedAccounts: Boolean,
+ autoSelectEnabled: Boolean,
+ ): AuthProvider.Google.GoogleSignInResult {
+ throw UnsupportedOperationException("Not used in this test")
+ }
+
+ override suspend fun clearCredentialState(
+ context: Context,
+ credentialManager: androidx.credentials.CredentialManager,
+ ) {
+ googleSignOutCalled = true
+ }
+ }
+
+ // Create instance with mock auth and inject test provider
+ val instance = FirebaseAuthUI.create(defaultApp, mockAuth)
+ instance.testCredentialManagerProvider = mockCredentialManagerProvider
+ val context = ApplicationProvider.getApplicationContext()
+
+ // Perform sign out
+ instance.signOut(context)
+
+ // Verify Google sign out was called
+ assertThat(googleSignOutCalled).isTrue()
+ verify(mockAuth).signOut()
+ }
+
+ @Test
+ fun `signOut() calls Facebook sign out when user provider is Facebook`() = runTest {
+ // Setup mock user with Facebook provider
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockUserInfo = mock(UserInfo::class.java)
+ `when`(mockUserInfo.providerId).thenReturn("facebook.com")
+ `when`(mockUser.providerId).thenReturn("facebook.com")
+ `when`(mockUser.providerData).thenReturn(listOf(mockUserInfo))
+
+ // Setup mock auth
+ val mockAuth = mock(FirebaseAuth::class.java)
+ `when`(mockAuth.currentUser).thenReturn(mockUser)
+ doNothing().`when`(mockAuth).signOut()
+
+ // Create mock login manager provider
+ var facebookSignOutCalled = false
+ val mockLoginManagerProvider = object : AuthProvider.Facebook.LoginManagerProvider {
+ override fun getCredential(token: String): com.google.firebase.auth.AuthCredential {
+ throw UnsupportedOperationException("Not used in this test")
+ }
+
+ override fun logOut() {
+ facebookSignOutCalled = true
+ }
+ }
+
+ // Create instance with mock auth and inject test provider
+ val instance = FirebaseAuthUI.create(defaultApp, mockAuth)
+ instance.testLoginManagerProvider = mockLoginManagerProvider
+ val context = ApplicationProvider.getApplicationContext()
+
+ // Perform sign out
+ instance.signOut(context)
+
+ // Verify Facebook sign out was called
+ assertThat(facebookSignOutCalled).isTrue()
+ verify(mockAuth).signOut()
+ }
+
+ @Test
+ fun `signOut() does not call Google or Facebook sign out when user provider is Email`() =
+ runTest {
+ // Setup mock user with Email provider
+ val mockUser = mock(FirebaseUser::class.java)
+ val mockUserInfo = mock(UserInfo::class.java)
+ `when`(mockUserInfo.providerId).thenReturn("password")
+ `when`(mockUser.providerId).thenReturn("password")
+ `when`(mockUser.providerData).thenReturn(listOf(mockUserInfo))
+
+ // Setup mock auth
+ val mockAuth = mock(FirebaseAuth::class.java)
+ `when`(mockAuth.currentUser).thenReturn(mockUser)
+ doNothing().`when`(mockAuth).signOut()
+
+ // Create mock providers
+ var googleSignOutCalled = false
+ val mockCredentialManagerProvider =
+ object : AuthProvider.Google.CredentialManagerProvider {
+ override suspend fun getGoogleCredential(
+ context: Context,
+ credentialManager: androidx.credentials.CredentialManager,
+ serverClientId: String,
+ filterByAuthorizedAccounts: Boolean,
+ autoSelectEnabled: Boolean,
+ ): AuthProvider.Google.GoogleSignInResult {
+ throw UnsupportedOperationException("Not used in this test")
+ }
+
+ override suspend fun clearCredentialState(
+ context: Context,
+ credentialManager: androidx.credentials.CredentialManager,
+ ) {
+ googleSignOutCalled = true
+ }
+ }
+
+ var facebookSignOutCalled = false
+ val mockLoginManagerProvider = object : AuthProvider.Facebook.LoginManagerProvider {
+ override fun getCredential(token: String): com.google.firebase.auth.AuthCredential {
+ throw UnsupportedOperationException("Not used in this test")
+ }
+
+ override fun logOut() {
+ facebookSignOutCalled = true
+ }
+ }
+
+ // Create instance with mock auth and inject test providers
+ val instance = FirebaseAuthUI.create(defaultApp, mockAuth)
+ instance.testCredentialManagerProvider = mockCredentialManagerProvider
+ instance.testLoginManagerProvider = mockLoginManagerProvider
+ val context = ApplicationProvider.getApplicationContext()
+
+ // Perform sign out
+ instance.signOut(context)
+
+ // Verify neither Google nor Facebook sign out was called
+ assertThat(googleSignOutCalled).isFalse()
+ assertThat(facebookSignOutCalled).isFalse()
+ verify(mockAuth).signOut()
+ }
+
// =============================================================================================
// Delete Account Tests
// =============================================================================================
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
index 304d3c3be..80cf85ad6 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/AuthUIConfigurationTest.kt
@@ -75,7 +75,7 @@ class AuthUIConfigurationTest {
assertThat(config.context).isEqualTo(applicationContext)
assertThat(config.providers).hasSize(1)
- assertThat(config.theme).isEqualTo(AuthUITheme.Default)
+ assertThat(config.theme).isNull()
assertThat(config.stringProvider).isInstanceOf(DefaultAuthUIStringProvider::class.java)
assertThat(config.locale).isNull()
assertThat(config.isCredentialManagerEnabled).isTrue()
@@ -463,7 +463,8 @@ class AuthUIConfigurationTest {
"passwordResetActionCodeSettings",
"isNewEmailAccountsAllowed",
"isDisplayNameRequired",
- "isProviderChoiceAlwaysShown"
+ "isProviderChoiceAlwaysShown",
+ "transitions"
)
val actualProperties = allProperties.map { it.name }.toSet()
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt
index b5109bc36..155de1f82 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProviderFirebaseAuthUI.kt
@@ -65,7 +65,7 @@ class FacebookAuthProviderFirebaseAuthUITest {
private lateinit var mockFirebaseAuth: FirebaseAuth
@Mock
- private lateinit var mockFBAuthCredentialProvider: AuthProvider.Facebook.CredentialProvider
+ private lateinit var mockFBAuthCredentialProvider: AuthProvider.Facebook.LoginManagerProvider
private lateinit var firebaseApp: FirebaseApp
private lateinit var applicationContext: Context
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProviderFirebaseAuthUITest.kt
index 3e5fdb981..1d027d9ea 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProviderFirebaseAuthUITest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProviderFirebaseAuthUITest.kt
@@ -131,9 +131,10 @@ class OAuthProviderFirebaseAuthUITest {
}
instance.signInWithProvider(
+ applicationContext,
config = config,
activity = mockActivity,
- provider = githubProvider
+ provider = githubProvider,
)
// Verify OAuth provider was built and used
@@ -186,6 +187,7 @@ class OAuthProviderFirebaseAuthUITest {
}
instance.signInWithProvider(
+ applicationContext,
config = config,
activity = mockActivity,
provider = yahooProvider
@@ -231,6 +233,7 @@ class OAuthProviderFirebaseAuthUITest {
try {
instance.signInWithProvider(
+ applicationContext,
config = config,
activity = mockActivity,
provider = githubProvider
@@ -271,6 +274,7 @@ class OAuthProviderFirebaseAuthUITest {
try {
instance.signInWithProvider(
+ applicationContext,
config = config,
activity = mockActivity,
provider = microsoftProvider
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt
index c836ac218..81386183c 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProviderFirebaseAuthUITest.kt
@@ -317,6 +317,7 @@ class PhoneAuthProviderFirebaseAuthUITest {
}
val result = instance.submitVerificationCode(
+ applicationContext,
config = config,
verificationId = "test-verification-id",
code = "123456",
@@ -359,6 +360,7 @@ class PhoneAuthProviderFirebaseAuthUITest {
}
val result = instance.signInWithPhoneAuthCredential(
+ applicationContext,
config = config,
credential = mockCredential
)
@@ -399,6 +401,7 @@ class PhoneAuthProviderFirebaseAuthUITest {
}
val result = instance.signInWithPhoneAuthCredential(
+ applicationContext,
config = config,
credential = mockCredential
)
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt
index c08702fea..46e6a1dc8 100644
--- a/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt
@@ -1,18 +1,33 @@
package com.firebase.ui.auth.configuration.theme
+import android.content.Context
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
+import androidx.compose.material3.ShapeDefaults
+import androidx.compose.material3.Text
import androidx.compose.material3.Typography
+import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.test.core.app.ApplicationProvider
+import com.firebase.ui.auth.configuration.AuthUIConfiguration
+import com.firebase.ui.auth.configuration.authUIConfiguration
+import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
+import com.firebase.ui.auth.ui.screens.FirebaseAuthScreen
import com.google.common.truth.Truth.assertThat
+import com.google.firebase.FirebaseApp
+import com.google.firebase.FirebaseOptions
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -26,6 +41,47 @@ class AuthUIThemeTest {
@get:Rule
val composeTestRule = createComposeRule()
+ private lateinit var applicationContext: Context
+
+ @Before
+ fun setup() {
+ applicationContext = ApplicationProvider.getApplicationContext()
+
+ // Clear any existing Firebase apps
+ FirebaseApp.getApps(applicationContext).forEach { app ->
+ app.delete()
+ }
+
+ // Initialize default FirebaseApp
+ FirebaseApp.initializeApp(
+ applicationContext,
+ FirebaseOptions.Builder()
+ .setApiKey("fake-api-key")
+ .setApplicationId("fake-app-id")
+ .setProjectId("fake-project-id")
+ .build()
+ )
+ }
+
+ private fun createTestConfiguration(theme: AuthUITheme? = null): AuthUIConfiguration {
+ return authUIConfiguration {
+ this.context = this@AuthUIThemeTest.applicationContext
+ this.theme = theme
+ providers {
+ provider(
+ AuthProvider.Email(
+ emailLinkActionCodeSettings = null,
+ passwordValidationRules = emptyList()
+ )
+ )
+ }
+ }
+ }
+
+ // ========================================================================
+ // Basic Theme Tests
+ // ========================================================================
+
@Test
fun `Default AuthUITheme applies to MaterialTheme`() {
val theme = AuthUITheme.Default
@@ -40,7 +96,229 @@ class AuthUIThemeTest {
}
@Test
- fun `fromMaterialTheme inherits client MaterialTheme values`() {
+ fun `AuthUITheme synchronizes with MaterialTheme`() {
+ val theme = AuthUITheme.DefaultDark
+
+ var authUIThemeColors: ColorScheme? = null
+ var materialThemeColors: ColorScheme? = null
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ authUIThemeColors = LocalAuthUITheme.current.colorScheme
+ materialThemeColors = MaterialTheme.colorScheme
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(authUIThemeColors).isEqualTo(materialThemeColors)
+ }
+
+ @Test
+ fun `AuthUITheme Default uses light color scheme`() {
+ val expectedLightColors = lightColorScheme()
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = AuthUITheme.Default) {
+ val colors = LocalAuthUITheme.current.colorScheme
+ assertThat(colors.primary).isEqualTo(expectedLightColors.primary)
+ assertThat(colors.background).isEqualTo(expectedLightColors.background)
+ assertThat(colors.surface).isEqualTo(expectedLightColors.surface)
+ }
+ }
+ }
+
+ @Test
+ fun `AuthUITheme DefaultDark uses dark color scheme`() {
+ val expectedDarkColors = darkColorScheme()
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = AuthUITheme.DefaultDark) {
+ val colors = LocalAuthUITheme.current.colorScheme
+ assertThat(colors.primary).isEqualTo(expectedDarkColors.primary)
+ assertThat(colors.background).isEqualTo(expectedDarkColors.background)
+ assertThat(colors.surface).isEqualTo(expectedDarkColors.surface)
+ }
+ }
+ }
+
+ // ========================================================================
+ // Theme Inheritance & Precedence Tests
+ // ========================================================================
+
+ @Test
+ fun `Configuration theme takes precedence over wrapper theme`() {
+ val wrapperTheme = AuthUITheme.DefaultDark
+ val configurationTheme = AuthUITheme.Default
+
+ var observedTheme: AuthUITheme? = null
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = wrapperTheme) {
+ CompositionLocalProvider(
+ LocalAuthUITheme provides (configurationTheme)
+ ) {
+ observedTheme = LocalAuthUITheme.current
+ }
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedTheme?.colorScheme).isEqualTo(configurationTheme.colorScheme)
+ }
+
+ @Test
+ fun `Wrapper theme applies when configuration theme is null`() {
+ val wrapperTheme = AuthUITheme.DefaultDark
+
+ var insideWrapperTheme: AuthUITheme? = null
+ var insideProviderTheme: AuthUITheme? = null
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = wrapperTheme) {
+ insideWrapperTheme = LocalAuthUITheme.current
+
+ // Simulate FirebaseAuthScreen's theme provision with null config.theme
+ CompositionLocalProvider(
+ LocalAuthUITheme provides (null ?: LocalAuthUITheme.current)
+ ) {
+ insideProviderTheme = LocalAuthUITheme.current
+ }
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(insideProviderTheme?.colorScheme).isEqualTo(wrapperTheme.colorScheme)
+ assertThat(insideWrapperTheme?.colorScheme).isEqualTo(insideProviderTheme?.colorScheme)
+ }
+
+ @Test
+ fun `Default theme applies when no theme specified`() {
+ var observedTheme: AuthUITheme? = null
+
+ composeTestRule.setContent {
+ // Simulate FirebaseAuthScreen's theme provision with null config.theme and no wrapper
+ CompositionLocalProvider(
+ LocalAuthUITheme provides (null ?: LocalAuthUITheme.current)
+ ) {
+ observedTheme = LocalAuthUITheme.current
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedTheme).isEqualTo(AuthUITheme.Default)
+ }
+
+ // ========================================================================
+ // Adaptive Theme Tests
+ // ========================================================================
+
+ @Test
+ fun `Adaptive theme resolves to Default or DefaultDark`() {
+ var adaptiveTheme: AuthUITheme? = null
+
+ composeTestRule.setContent {
+ adaptiveTheme = AuthUITheme.Adaptive
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(adaptiveTheme).isIn(listOf(AuthUITheme.Default, AuthUITheme.DefaultDark))
+ }
+
+ @Test
+ fun `Adaptive theme in configuration applies correctly`() {
+ var observedTheme: AuthUITheme? = null
+ var adaptiveThemeResolved: AuthUITheme? = null
+
+ composeTestRule.setContent {
+ adaptiveThemeResolved = AuthUITheme.Adaptive
+
+ CompositionLocalProvider(
+ LocalAuthUITheme provides adaptiveThemeResolved!!
+ ) {
+ observedTheme = LocalAuthUITheme.current
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedTheme?.colorScheme).isEqualTo(adaptiveThemeResolved?.colorScheme)
+ }
+
+ // ========================================================================
+ // Customization Tests
+ // ========================================================================
+
+ @Test
+ fun `Copy with custom provider button shape applies correctly`() {
+ val customShape = ShapeDefaults.ExtraLarge
+ val customTheme = AuthUITheme.Default.copy(
+ providerButtonShape = customShape
+ )
+
+ var observedProviderButtonShape: Shape? = null
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalAuthUITheme provides customTheme
+ ) {
+ observedProviderButtonShape = LocalAuthUITheme.current.providerButtonShape
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedProviderButtonShape).isEqualTo(customShape)
+ }
+
+ @Test
+ fun `Copy preserves other properties`() {
+ val customStyles = mapOf(
+ "google.com" to AuthUITheme.ProviderStyle(
+ icon = null,
+ backgroundColor = Color.Red,
+ contentColor = Color.White
+ )
+ )
+
+ val original = AuthUITheme.Default.copy(
+ providerButtonShape = RoundedCornerShape(12.dp),
+ providerStyles = customStyles
+ )
+
+ val copied = original.copy(
+ providerButtonShape = RoundedCornerShape(20.dp)
+ )
+
+ var observedProviderStyles: Map? = null
+ var observedProviderButtonShape: Shape? = null
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalAuthUITheme provides copied
+ ) {
+ observedProviderStyles = LocalAuthUITheme.current.providerStyles
+ observedProviderButtonShape = LocalAuthUITheme.current.providerButtonShape
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedProviderButtonShape).isEqualTo(RoundedCornerShape(20.dp))
+ assertThat(observedProviderStyles).containsKey("google.com")
+ assertThat(observedProviderStyles?.get("google.com")?.backgroundColor).isEqualTo(Color.Red)
+ }
+
+ // ========================================================================
+ // fromMaterialTheme Tests
+ // ========================================================================
+
+ @Test
+ fun `fromMaterialTheme inherits MaterialTheme values`() {
val appLightColorScheme = lightColorScheme(
primary = Color(0xFF6650a4),
secondary = Color(0xFF625b71),
@@ -68,14 +346,78 @@ class AuthUIThemeTest {
AuthUITheme(
theme = AuthUITheme.fromMaterialTheme()
) {
- assertThat(MaterialTheme.colorScheme)
- .isEqualTo(appLightColorScheme)
- assertThat(MaterialTheme.typography)
- .isEqualTo(appTypography)
- assertThat(MaterialTheme.shapes)
- .isEqualTo(appShapes)
+ assertThat(MaterialTheme.colorScheme).isEqualTo(appLightColorScheme)
+ assertThat(MaterialTheme.typography).isEqualTo(appTypography)
+ assertThat(MaterialTheme.shapes).isEqualTo(appShapes)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `fromMaterialTheme inherits all properties completely`() {
+ val customColorScheme = lightColorScheme(
+ primary = Color(0xFFFF0000),
+ background = Color(0xFFFFFFFF)
+ )
+ val customTypography = Typography(
+ bodyLarge = TextStyle(fontSize = 18.sp)
+ )
+ val customShapes = Shapes(
+ small = RoundedCornerShape(8.dp)
+ )
+
+ var observedColorScheme: ColorScheme? = null
+ var observedTypography: Typography? = null
+ var observedShapes: Shapes? = null
+
+ composeTestRule.setContent {
+ MaterialTheme(
+ colorScheme = customColorScheme,
+ typography = customTypography,
+ shapes = customShapes
+ ) {
+ val theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = RoundedCornerShape(16.dp)
+ )
+
+ CompositionLocalProvider(
+ LocalAuthUITheme provides theme
+ ) {
+ observedColorScheme = LocalAuthUITheme.current.colorScheme
+ observedTypography = LocalAuthUITheme.current.typography
+ observedShapes = LocalAuthUITheme.current.shapes
}
}
}
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedColorScheme?.primary).isEqualTo(Color(0xFFFF0000))
+ assertThat(observedTypography).isEqualTo(customTypography)
+ assertThat(observedShapes).isEqualTo(customShapes)
+ }
+
+ @Test
+ fun `fromMaterialTheme with custom provider button shape`() {
+ val customShape = RoundedCornerShape(16.dp)
+
+ var observedProviderButtonShape: Shape? = null
+
+ composeTestRule.setContent {
+ MaterialTheme {
+ val theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = customShape
+ )
+
+ AuthUITheme(theme = theme) {
+ observedProviderButtonShape = LocalAuthUITheme.current.providerButtonShape
+ }
+ }
+ }
+
+ composeTestRule.waitForIdle()
+
+ assertThat(observedProviderButtonShape).isEqualTo(customShape)
}
}
diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/theme/ProviderButtonShapeCustomizationTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/ProviderButtonShapeCustomizationTest.kt
new file mode 100644
index 000000000..6c01a716f
--- /dev/null
+++ b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/ProviderButtonShapeCustomizationTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.configuration.theme
+
+import android.content.Context
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
+import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
+import com.firebase.ui.auth.ui.components.AuthProviderButton
+import com.google.common.truth.Truth.assertThat
+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
+
+/**
+ * Tests for provider button shape customization features.
+ *
+ * Verifies that:
+ * - Custom shapes can be set globally for all provider buttons
+ * - Individual provider styles can override the global shape
+ * - Shapes properly inherit through the composition local system
+ */
+@Config(sdk = [34])
+@RunWith(RobolectricTestRunner::class)
+class ProviderButtonShapeCustomizationTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun `providerButtonShape applies to all provider buttons`() {
+ val customShape = RoundedCornerShape(16.dp)
+ val theme = AuthUITheme(
+ colorScheme = lightColorScheme(),
+ typography = androidx.compose.material3.Typography(),
+ shapes = androidx.compose.material3.Shapes(),
+ providerButtonShape = customShape
+ )
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ val currentTheme = LocalAuthUITheme.current
+ assertThat(currentTheme.providerButtonShape).isEqualTo(customShape)
+ }
+ }
+ }
+
+ @Test
+ fun `individual provider style shape overrides global providerButtonShape`() {
+ val globalShape = RoundedCornerShape(8.dp)
+ val googleSpecificShape = RoundedCornerShape(24.dp)
+
+ val customProviderStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = googleSpecificShape
+ )
+ )
+
+ val theme = AuthUITheme(
+ colorScheme = lightColorScheme(),
+ typography = androidx.compose.material3.Typography(),
+ shapes = androidx.compose.material3.Shapes(),
+ providerButtonShape = globalShape,
+ providerStyles = customProviderStyles
+ )
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ val currentTheme = LocalAuthUITheme.current
+ val googleStyle = currentTheme.providerStyles["google.com"]
+ assertThat(googleStyle).isNotNull()
+ assertThat(googleStyle?.shape).isEqualTo(googleSpecificShape)
+ }
+ }
+ }
+
+ @Test
+ fun `fromMaterialTheme accepts providerButtonShape parameter`() {
+ val customShape = RoundedCornerShape(12.dp)
+
+ composeTestRule.setContent {
+ val theme = AuthUITheme.fromMaterialTheme(
+ providerButtonShape = customShape
+ )
+
+ assertThat(theme.providerButtonShape).isEqualTo(customShape)
+ }
+ }
+
+ @Test
+ fun `ProviderStyleDefaults are publicly accessible`() {
+ // Verify all default provider styles are accessible
+ assertThat(ProviderStyleDefaults.Google).isNotNull()
+ assertThat(ProviderStyleDefaults.Facebook).isNotNull()
+ assertThat(ProviderStyleDefaults.Twitter).isNotNull()
+ assertThat(ProviderStyleDefaults.Github).isNotNull()
+ assertThat(ProviderStyleDefaults.Email).isNotNull()
+ assertThat(ProviderStyleDefaults.Phone).isNotNull()
+ assertThat(ProviderStyleDefaults.Anonymous).isNotNull()
+ assertThat(ProviderStyleDefaults.Microsoft).isNotNull()
+ assertThat(ProviderStyleDefaults.Yahoo).isNotNull()
+ assertThat(ProviderStyleDefaults.Apple).isNotNull()
+ }
+
+ @Test
+ fun `ProviderStyle is a data class with copy method`() {
+ val original = ProviderStyleDefaults.Google
+ val customShape = RoundedCornerShape(20.dp)
+
+ val modified = original.copy(shape = customShape)
+
+ assertThat(modified.shape).isEqualTo(customShape)
+ assertThat(modified.backgroundColor).isEqualTo(original.backgroundColor)
+ assertThat(modified.contentColor).isEqualTo(original.contentColor)
+ assertThat(modified.icon).isEqualTo(original.icon)
+ }
+
+ @Test
+ fun `AuthProviderButton respects theme providerButtonShape`() {
+ val customShape = RoundedCornerShape(16.dp)
+ val theme = AuthUITheme(
+ colorScheme = lightColorScheme(),
+ typography = androidx.compose.material3.Typography(),
+ shapes = androidx.compose.material3.Shapes(),
+ providerButtonShape = customShape
+ )
+
+ val provider = AuthProvider.Google(scopes = emptyList(), serverClientId = null)
+ val stringProvider = DefaultAuthUIStringProvider(context)
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ AuthProviderButton(
+ provider = provider,
+ onClick = { },
+ stringProvider = stringProvider
+ )
+ // Button should use customShape internally
+ val currentTheme = LocalAuthUITheme.current
+ assertThat(currentTheme.providerButtonShape).isEqualTo(customShape)
+ }
+ }
+ }
+
+ @Test
+ fun `default shape is used when no custom shape is provided`() {
+ val theme = AuthUITheme(
+ colorScheme = lightColorScheme(),
+ typography = androidx.compose.material3.Typography(),
+ shapes = androidx.compose.material3.Shapes(),
+ providerButtonShape = null
+ )
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ val currentTheme = LocalAuthUITheme.current
+ assertThat(currentTheme.providerButtonShape).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun `custom provider styles with null shapes use global providerButtonShape`() {
+ val globalShape = RoundedCornerShape(12.dp)
+
+ val customProviderStyles = mapOf(
+ "google.com" to ProviderStyleDefaults.Google.copy(
+ shape = null // Explicitly set to null to inherit global shape
+ )
+ )
+
+ val theme = AuthUITheme(
+ colorScheme = lightColorScheme(),
+ typography = androidx.compose.material3.Typography(),
+ shapes = androidx.compose.material3.Shapes(),
+ providerButtonShape = globalShape,
+ providerStyles = customProviderStyles
+ )
+
+ composeTestRule.setContent {
+ AuthUITheme(theme = theme) {
+ val currentTheme = LocalAuthUITheme.current
+ val googleStyle = currentTheme.providerStyles["google.com"]
+ // Shape should be null in the style, but button will use global shape
+ assertThat(googleStyle?.shape).isNull()
+ assertThat(currentTheme.providerButtonShape).isEqualTo(globalShape)
+ }
+ }
+ }
+}
diff --git a/auth/src/test/java/com/firebase/ui/auth/ui/components/AuthProviderButtonTest.kt b/auth/src/test/java/com/firebase/ui/auth/ui/components/AuthProviderButtonTest.kt
index d45176e94..a91518522 100644
--- a/auth/src/test/java/com/firebase/ui/auth/ui/components/AuthProviderButtonTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/ui/components/AuthProviderButtonTest.kt
@@ -34,6 +34,7 @@ import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.configuration.theme.AuthUITheme
+import com.firebase.ui.auth.configuration.theme.ProviderStyleDefaults
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -370,7 +371,7 @@ class AuthProviderButtonTest {
@Test
fun `AuthProviderButton uses custom style when provided`() {
val provider = AuthProvider.Google(scopes = emptyList(), serverClientId = null)
- val customStyle = AuthUITheme.Default.providerStyles[Provider.FACEBOOK.id]
+ val customStyle = ProviderStyleDefaults.Facebook
composeTestRule.setContent {
AuthProviderButton(
@@ -385,10 +386,12 @@ class AuthProviderButtonTest {
.onNodeWithText(context.getString(R.string.fui_sign_in_with_google))
.assertIsDisplayed()
- val resolvedStyle = resolveProviderStyle(provider, customStyle)
- assertThat(resolvedStyle).isEqualTo(customStyle)
- assertThat(resolvedStyle)
- .isNotEqualTo(AuthUITheme.Default.providerStyles[Provider.GOOGLE.id])
+ val resolvedStyle = resolveProviderStyle(provider, customStyle, ProviderStyleDefaults.default, null)
+ assertThat(resolvedStyle.backgroundColor).isEqualTo(customStyle.backgroundColor)
+ assertThat(resolvedStyle.contentColor).isEqualTo(customStyle.contentColor)
+ assertThat(resolvedStyle.icon).isEqualTo(customStyle.icon)
+ assertThat(resolvedStyle.backgroundColor)
+ .isNotEqualTo(ProviderStyleDefaults.Google.backgroundColor)
}
@Test
@@ -423,14 +426,14 @@ class AuthProviderButtonTest {
composeTestRule.onNodeWithContentDescription(customLabel)
.assertIsDisplayed()
- val resolvedStyle = resolveProviderStyle(provider, null)
+ val resolvedStyle = resolveProviderStyle(provider, null, ProviderStyleDefaults.default, null)
assertThat(resolvedStyle).isNotNull()
assertThat(resolvedStyle.backgroundColor).isEqualTo(customColor)
assertThat(resolvedStyle.contentColor).isEqualTo(customContentColor)
assertThat(resolvedStyle.icon).isEqualTo(customIcon)
- val googleDefaultStyle = AuthUITheme.Default.providerStyles["google.com"]
- assertThat(resolvedStyle).isNotEqualTo(googleDefaultStyle)
+ val googleDefaultStyle = ProviderStyleDefaults.Google
+ assertThat(resolvedStyle.backgroundColor).isNotEqualTo(googleDefaultStyle.backgroundColor)
}
@Test
@@ -458,11 +461,10 @@ class AuthProviderButtonTest {
composeTestRule.onNodeWithText(customLabel)
.assertIsDisplayed()
- val resolvedStyle = resolveProviderStyle(provider, null)
- val googleDefaultStyle = AuthUITheme.Default.providerStyles["google.com"]
+ val resolvedStyle = resolveProviderStyle(provider, null, ProviderStyleDefaults.default, null)
+ val googleDefaultStyle = ProviderStyleDefaults.Google
- assertThat(googleDefaultStyle).isNotNull()
- assertThat(resolvedStyle.backgroundColor).isEqualTo(googleDefaultStyle!!.backgroundColor)
+ assertThat(resolvedStyle.backgroundColor).isEqualTo(googleDefaultStyle.backgroundColor)
assertThat(resolvedStyle.contentColor).isEqualTo(googleDefaultStyle.contentColor)
assertThat(resolvedStyle.icon).isEqualTo(googleDefaultStyle.icon)
}
@@ -506,7 +508,7 @@ class AuthProviderButtonTest {
contentColor = customContentColor
)
- val resolvedStyle = resolveProviderStyle(provider, null)
+ val resolvedStyle = resolveProviderStyle(provider, null, ProviderStyleDefaults.default, null)
assertThat(resolvedStyle).isNotNull()
assertThat(resolvedStyle.backgroundColor).isEqualTo(customColor)
@@ -526,7 +528,7 @@ class AuthProviderButtonTest {
contentColor = Color.White
)
- val resolvedStyle = resolveProviderStyle(provider, null)
+ val resolvedStyle = resolveProviderStyle(provider, null, ProviderStyleDefaults.default, null)
assertThat(resolvedStyle).isNotNull()
assertThat(resolvedStyle.icon).isNull()
@@ -538,9 +540,10 @@ class AuthProviderButtonTest {
fun `resolveProviderStyle provides fallback for unknown provider`() {
val provider = object : AuthProvider(providerId = "unknown.provider", providerName = "Generic Provider") {}
- val resolvedStyle = resolveProviderStyle(provider, null)
+ val resolvedStyle = resolveProviderStyle(provider, null, ProviderStyleDefaults.default, null)
assertThat(resolvedStyle).isNotNull()
- assertThat(resolvedStyle).isEqualTo(AuthUITheme.ProviderStyle.Empty)
+ assertThat(resolvedStyle.backgroundColor).isEqualTo(AuthUITheme.ProviderStyle.Empty.backgroundColor)
+ assertThat(resolvedStyle.contentColor).isEqualTo(AuthUITheme.ProviderStyle.Empty.contentColor)
}
}
\ No newline at end of file
diff --git a/auth/src/test/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialogLogicTest.kt b/auth/src/test/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialogLogicTest.kt
index adf1a939b..ca53181eb 100644
--- a/auth/src/test/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialogLogicTest.kt
+++ b/auth/src/test/java/com/firebase/ui/auth/ui/components/ErrorRecoveryDialogLogicTest.kt
@@ -56,7 +56,43 @@ class ErrorRecoveryDialogLogicTest {
// Act
val message = getRecoveryMessage(error, mockStringProvider)
- // Assert
+ // Assert - Should show the actual error message since it's not the generic fallback
+ Truth.assertThat(message).isEqualTo("Invalid credentials")
+ }
+
+ @Test
+ fun `getRecoveryMessage returns actual Firebase error message for InvalidCredentialsException`() {
+ // Arrange - Simulate a real Firebase error message
+ val error = AuthException.InvalidCredentialsException("The email address is badly formatted.")
+
+ // Act
+ val message = getRecoveryMessage(error, mockStringProvider)
+
+ // Assert - Should show the actual Firebase error, not the generic message
+ Truth.assertThat(message).isEqualTo("The email address is badly formatted.")
+ }
+
+ @Test
+ fun `getRecoveryMessage returns generic message for InvalidCredentialsException with generic error text`() {
+ // Arrange - When error message is the generic fallback
+ val error = AuthException.InvalidCredentialsException("Invalid credentials provided")
+
+ // Act
+ val message = getRecoveryMessage(error, mockStringProvider)
+
+ // Assert - Should show the localized generic message
+ Truth.assertThat(message).isEqualTo("Incorrect password.")
+ }
+
+ @Test
+ fun `getRecoveryMessage returns generic message for InvalidCredentialsException with blank message`() {
+ // Arrange
+ val error = AuthException.InvalidCredentialsException("")
+
+ // Act
+ val message = getRecoveryMessage(error, mockStringProvider)
+
+ // Assert - Should show the localized generic message
Truth.assertThat(message).isEqualTo("Incorrect password.")
}
@@ -245,7 +281,11 @@ class ErrorRecoveryDialogLogicTest {
private fun getRecoveryMessage(error: AuthException, stringProvider: AuthUIStringProvider): String {
return when (error) {
is AuthException.NetworkException -> stringProvider.networkErrorRecoveryMessage
- is AuthException.InvalidCredentialsException -> stringProvider.invalidCredentialsRecoveryMessage
+ is AuthException.InvalidCredentialsException -> {
+ // Use the actual error message from Firebase if available, otherwise fallback to generic message
+ error.message?.takeIf { it.isNotBlank() && it != "Invalid credentials provided" }
+ ?: stringProvider.invalidCredentialsRecoveryMessage
+ }
is AuthException.UserNotFoundException -> stringProvider.userNotFoundRecoveryMessage
is AuthException.WeakPasswordException -> {
val baseMessage = stringProvider.weakPasswordRecoveryMessage
diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt
index 740e3213e..f82a29287 100644
--- a/buildSrc/src/main/kotlin/Config.kt
+++ b/buildSrc/src/main/kotlin/Config.kt
@@ -69,7 +69,7 @@ object Config {
}
object Firebase {
- const val bom = "com.google.firebase:firebase-bom:33.9.0"
+ const val bom = "com.google.firebase:firebase-bom:34.7.0"
const val auth = "com.google.firebase:firebase-auth"
const val database = "com.google.firebase:firebase-database"
const val firestore = "com.google.firebase:firebase-firestore"
diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/AccessibilityTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/AccessibilityTest.kt
index b79ab3772..f7ca19e25 100644
--- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/AccessibilityTest.kt
+++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/AccessibilityTest.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.LayoutDirection
import androidx.test.core.app.ApplicationProvider
+import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.authUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider
@@ -28,6 +29,7 @@ import com.firebase.ui.auth.ui.components.AuthTextField
import com.firebase.ui.auth.ui.components.CountrySelector
import com.firebase.ui.auth.ui.components.QrCodeImage
import com.firebase.ui.auth.ui.components.VerificationCodeInputField
+import com.firebase.ui.auth.ui.screens.email.EmailAuthScreen
import com.firebase.ui.auth.ui.screens.email.SignInUI
import com.firebase.ui.auth.ui.screens.phone.EnterPhoneNumberUI
import com.firebase.ui.auth.util.CountryUtils
@@ -194,6 +196,7 @@ class AccessibilityTest {
onEmailChange = {},
onPasswordChange = {},
onSignInClick = {},
+ onRetrievedCredential = {},
onGoToEmailLinkSignIn = {},
onGoToSignUp = {},
onGoToResetPassword = {},
@@ -284,6 +287,7 @@ class AccessibilityTest {
password = "",
onEmailChange = {},
onPasswordChange = {},
+ onRetrievedCredential = {},
onSignInClick = {},
onGoToEmailLinkSignIn = {},
onGoToSignUp = {},
diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt
index ba68bad9a..59c5d829a 100644
--- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt
+++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt
@@ -181,6 +181,7 @@ class AnonymousAuthScreenTest {
)
}
isAnonymousUpgradeEnabled = true
+ isCredentialManagerEnabled = false
}
// Track auth state changes
diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt
index 50fca4728..423aa8d62 100644
--- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt
+++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt
@@ -24,6 +24,10 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CredentialManager
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import com.firebase.ui.auth.AuthState
@@ -35,6 +39,8 @@ 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 com.firebase.ui.auth.credentialmanager.CredentialManagerProvider
+import com.firebase.ui.auth.credentialmanager.PasswordCredentialHandler
import com.firebase.ui.auth.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
import com.firebase.ui.auth.testutil.EmailLinkTestActivity
import com.firebase.ui.auth.testutil.EmulatorAuthApi
@@ -43,18 +49,27 @@ import com.firebase.ui.auth.testutil.verifyEmailInEmulator
import com.google.common.truth.Truth.assertThat
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
-import com.google.firebase.auth.ActionCodeSettings
import com.google.firebase.auth.actionCodeSettings
+import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeast
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
+import androidx.credentials.PasswordCredential as AndroidPasswordCredential
@Config(sdk = [34])
@RunWith(RobolectricTestRunner::class)
@@ -69,9 +84,14 @@ class EmailAuthScreenTest {
private lateinit var authUI: FirebaseAuthUI
private lateinit var emulatorApi: EmulatorAuthApi
+ @Mock
+ private lateinit var mockCredentialManager: CredentialManager
+
+ private lateinit var closeable: AutoCloseable
+
@Before
fun setUp() {
- MockitoAnnotations.openMocks(this)
+ closeable = MockitoAnnotations.openMocks(this)
applicationContext = ApplicationProvider.getApplicationContext()
@@ -104,15 +124,28 @@ class EmailAuthScreenTest {
// Clear emulator data
emulatorApi.clearEmulatorData()
+
+ // Set up test credential manager provider
+ PasswordCredentialHandler.testCredentialManagerProvider =
+ object : CredentialManagerProvider {
+ override fun getCredentialManager(context: Context): CredentialManager {
+ return mockCredentialManager
+ }
+ }
}
@After
fun tearDown() {
+ closeable.close()
+
// Clean up after each test to prevent test pollution
FirebaseAuthUI.clearInstanceCache()
// Clear emulator data
emulatorApi.clearEmulatorData()
+
+ // Clear test credential manager provider
+ PasswordCredentialHandler.testCredentialManagerProvider = null
}
@Test
@@ -127,6 +160,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
composeAndroidTestRule.setContent {
@@ -166,6 +200,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
// Track auth state changes
@@ -259,6 +294,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
// Track auth state changes
@@ -333,6 +369,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
// Track auth state changes
@@ -422,6 +459,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
// Track auth state changes
@@ -514,6 +552,7 @@ class EmailAuthScreenTest {
)
)
}
+ isCredentialManagerEnabled = false
}
// Track auth state changes and email link (lifted state)
@@ -620,25 +659,26 @@ class EmailAuthScreenTest {
// Use ActivityScenario to launch EmailLinkTestActivity with the deep link intent
// This properly simulates the Android deep link flow - when a user clicks the email link,
// Android launches the app with an ACTION_VIEW intent
- val extractedEmailLink = ActivityScenario.launch(deepLinkIntent).use { scenario ->
- var emailLinkFromIntent: String? = null
+ val extractedEmailLink =
+ ActivityScenario.launch(deepLinkIntent).use { scenario ->
+ var emailLinkFromIntent: String? = null
- scenario.onActivity { activity ->
- // Verify the intent was received correctly
- assertThat(activity.intent.action).isEqualTo(Intent.ACTION_VIEW)
- assertThat(activity.intent.data).isEqualTo(deepLinkUri)
+ scenario.onActivity { activity ->
+ // Verify the intent was received correctly
+ assertThat(activity.intent.action).isEqualTo(Intent.ACTION_VIEW)
+ assertThat(activity.intent.data).isEqualTo(deepLinkUri)
- // Verify the activity extracted the email link
- assertThat(activity.emailLinkFromIntent).isNotNull()
- assertThat(activity.emailLinkFromIntent).isEqualTo(emailLinkFromEmulator)
+ // Verify the activity extracted the email link
+ assertThat(activity.emailLinkFromIntent).isNotNull()
+ assertThat(activity.emailLinkFromIntent).isEqualTo(emailLinkFromEmulator)
- emailLinkFromIntent = activity.emailLinkFromIntent
+ emailLinkFromIntent = activity.emailLinkFromIntent
- println("TEST: Email link extracted by activity: $emailLinkFromIntent")
- }
+ println("TEST: Email link extracted by activity: $emailLinkFromIntent")
+ }
- emailLinkFromIntent
- }
+ emailLinkFromIntent
+ }
requireNotNull(extractedEmailLink) { "Failed to extract email link from intent" }
@@ -672,6 +712,340 @@ class EmailAuthScreenTest {
.assertIsDisplayed()
}
+ @Test
+ fun `sign up saves credential, then sign in retrieves it and auto-signs in`() = runBlocking {
+ val name = "Credential Test User"
+ val email = "credential-test-${System.currentTimeMillis()}@example.com"
+ val password = "Test@1234"
+
+ // Mock credential manager responses
+ val mockPasswordCredential = mock()
+ whenever(mockPasswordCredential.id).thenReturn(email)
+ whenever(mockPasswordCredential.password).thenReturn(password)
+
+ val mockCredentialResponse = mock()
+ whenever(mockCredentialResponse.credential).thenReturn(mockPasswordCredential)
+
+ // Mock successful credential save
+ whenever(mockCredentialManager.createCredential(any(), any()))
+ .thenReturn(mock())
+
+ // Mock successful credential retrieval
+ whenever(mockCredentialManager.getCredential(any(), any()))
+ .thenReturn(mockCredentialResponse)
+
+ val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(
+ AuthProvider.Email(
+ emailLinkActionCodeSettings = null,
+ passwordValidationRules = emptyList()
+ )
+ )
+ }
+ }
+
+ // Track auth state changes
+ var currentAuthState: AuthState = AuthState.Idle
+
+ composeAndroidTestRule.setContent {
+ TestFirebaseAuthScreen(configuration = configuration, authUI = authUI)
+ val authState by authUI.authStateFlow().collectAsState(AuthState.Idle)
+ currentAuthState = authState
+ }
+
+ // STEP 1: Sign up and verify credential saved
+ println("TEST: Starting sign-up flow...")
+
+ // Click on email provider
+ composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail)
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.waitForIdle()
+
+ // Click sign-up
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .assertIsDisplayed()
+ .performClick()
+
+ // Fill in sign-up form
+ composeAndroidTestRule.onNodeWithText(stringProvider.emailHint)
+ .performTextInput(email)
+ composeAndroidTestRule.onNodeWithText(stringProvider.nameHint)
+ .performTextInput(name)
+ composeAndroidTestRule.onNodeWithText(stringProvider.passwordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.confirmPasswordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .performScrollTo()
+ .performClick()
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Wait for sign-up to complete
+ composeAndroidTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
+ shadowOf(Looper.getMainLooper()).idle()
+ currentAuthState is AuthState.RequiresEmailVerification
+ }
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Verify user was created
+ assertThat(authUI.auth.currentUser).isNotNull()
+ assertThat(authUI.auth.currentUser!!.email).isEqualTo(email)
+
+ // Verify credentials were saved
+ verify(mockCredentialManager, times(1)).createCredential(
+ any(),
+ any()
+ )
+ println("TEST: Sign-up complete, credentials saved")
+
+ // STEP 2: Sign out to test credential retrieval
+ println("TEST: Signing out to test credential retrieval...")
+ authUI.auth.signOut()
+ shadowOf(Looper.getMainLooper()).idle()
+ composeAndroidTestRule.waitForIdle()
+ assertThat(authUI.auth.currentUser).isNull()
+
+ // STEP 3: Navigate to SignInUI screen to trigger credential retrieval
+ println("TEST: Navigating to sign-in screen to trigger credential retrieval...")
+
+ // Click on email provider to show SignInUI, which will trigger auto-retrieval
+ composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail)
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.waitForIdle()
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // SignInUI's LaunchedEffect should now trigger credential retrieval and auto-sign-in
+ println("TEST: Waiting for automatic credential retrieval and auto-sign-in...")
+
+ // Wait for auto-sign-in to complete
+ composeAndroidTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
+ shadowOf(Looper.getMainLooper()).idle()
+ currentAuthState is AuthState.RequiresEmailVerification
+ }
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Verify credentials were retrieved
+ verify(mockCredentialManager, times(1)).getCredential(any(), any())
+
+ // Verify auto-sign-in succeeded
+ assertThat(authUI.auth.currentUser).isNotNull()
+ assertThat(authUI.auth.currentUser!!.email).isEqualTo(email)
+
+ println("TEST: Credential retrieval and auto-sign-in successful")
+ }
+
+ @Test
+ fun `sign in with retrieved credential does not prompt to save again`() = runBlocking {
+ val name = "No Duplicate Save Test User"
+ val email = "no-duplicate-${System.currentTimeMillis()}@example.com"
+ val password = "Test@1234"
+
+ // Mock credential manager responses
+ val mockPasswordCredential = mock()
+ whenever(mockPasswordCredential.id).thenReturn(email)
+ whenever(mockPasswordCredential.password).thenReturn(password)
+
+ val mockCredentialResponse = mock()
+ whenever(mockCredentialResponse.credential).thenReturn(mockPasswordCredential)
+
+ // Mock successful credential save (should only be called once during sign-up)
+ whenever(mockCredentialManager.createCredential(any(), any()))
+ .thenReturn(mock())
+
+ // Mock successful credential retrieval
+ whenever(mockCredentialManager.getCredential(any(), any()))
+ .thenReturn(mockCredentialResponse)
+
+ val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(
+ AuthProvider.Email(
+ emailLinkActionCodeSettings = null,
+ passwordValidationRules = emptyList()
+ )
+ )
+ }
+ }
+
+ var currentAuthState: AuthState = AuthState.Idle
+
+ composeAndroidTestRule.setContent {
+ TestFirebaseAuthScreen(configuration = configuration, authUI = authUI)
+ val authState by authUI.authStateFlow().collectAsState(AuthState.Idle)
+ currentAuthState = authState
+ }
+
+ // STEP 1: Sign up and save credential
+ println("TEST: Starting sign-up flow...")
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail)
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.waitForIdle()
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.emailHint)
+ .performTextInput(email)
+ composeAndroidTestRule.onNodeWithText(stringProvider.nameHint)
+ .performTextInput(name)
+ composeAndroidTestRule.onNodeWithText(stringProvider.passwordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.confirmPasswordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .performScrollTo()
+ .performClick()
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ composeAndroidTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
+ shadowOf(Looper.getMainLooper()).idle()
+ currentAuthState is AuthState.RequiresEmailVerification
+ }
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ assertThat(authUI.auth.currentUser).isNotNull()
+ assertThat(authUI.auth.currentUser!!.email).isEqualTo(email)
+
+ // Verify credentials were saved during sign-up (first call)
+ verify(mockCredentialManager, times(1)).createCredential(any(), any())
+ println("TEST: Sign-up complete, credentials saved (createCredential called once)")
+
+ // STEP 2: Sign out
+ println("TEST: Signing out...")
+ authUI.auth.signOut()
+ shadowOf(Looper.getMainLooper()).idle()
+ composeAndroidTestRule.waitForIdle()
+ assertThat(authUI.auth.currentUser).isNull()
+
+ // STEP 3: Navigate to SignInUI to trigger credential retrieval
+ println("TEST: Navigating to sign-in screen...")
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail)
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.waitForIdle()
+ shadowOf(Looper.getMainLooper()).idle()
+
+ println("TEST: Waiting for automatic credential retrieval and auto-sign-in...")
+
+ composeAndroidTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
+ shadowOf(Looper.getMainLooper()).idle()
+ currentAuthState is AuthState.RequiresEmailVerification
+ }
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Verify credentials were retrieved (may be called multiple times due to navigation/remounting)
+ verify(mockCredentialManager, atLeast(1)).getCredential(any(), any())
+
+ // Verify auto-sign-in succeeded
+ assertThat(authUI.auth.currentUser).isNotNull()
+ assertThat(authUI.auth.currentUser!!.email).isEqualTo(email)
+
+ // CRITICAL: Verify createCredential was NOT called again (still only 1 time from sign-up)
+ // This is the main point of this test - verifying skipCredentialSave logic works
+ verify(mockCredentialManager, times(1)).createCredential(any(), any())
+ println("TEST: Verified no duplicate save prompt (createCredential still called only once)")
+ }
+
+ @Test
+ fun `credential manager disabled skips save and retrieve`() = runBlocking {
+ val name = "Disabled Test User"
+ val email = "disabled-cm-${System.currentTimeMillis()}@example.com"
+ val password = "Test@1234"
+
+ val configuration = authUIConfiguration {
+ context = applicationContext
+ providers {
+ provider(
+ AuthProvider.Email(
+ emailLinkActionCodeSettings = null,
+ passwordValidationRules = emptyList()
+ )
+ )
+ }
+ isCredentialManagerEnabled = false // DISABLED
+ }
+
+ var currentAuthState: AuthState = AuthState.Idle
+
+ composeAndroidTestRule.setContent {
+ TestFirebaseAuthScreen(configuration = configuration, authUI = authUI)
+ val authState by authUI.authStateFlow().collectAsState(AuthState.Idle)
+ currentAuthState = authState
+ }
+
+ // Sign up
+ composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail)
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.waitForIdle()
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .assertIsDisplayed()
+ .performClick()
+
+ composeAndroidTestRule.onNodeWithText(stringProvider.emailHint)
+ .performTextInput(email)
+ composeAndroidTestRule.onNodeWithText(stringProvider.nameHint)
+ .performTextInput(name)
+ composeAndroidTestRule.onNodeWithText(stringProvider.passwordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.confirmPasswordHint)
+ .performScrollTo()
+ .performTextInput(password)
+ composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
+ .performScrollTo()
+ .performClick()
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Wait for sign-up
+ composeAndroidTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
+ shadowOf(Looper.getMainLooper()).idle()
+ currentAuthState is AuthState.RequiresEmailVerification
+ }
+
+ shadowOf(Looper.getMainLooper()).idle()
+
+ // Verify credentials were not saved
+ verify(mockCredentialManager, never()).createCredential(
+ any(),
+ any()
+ )
+
+ // Verify user created
+ assertThat(authUI.auth.currentUser).isNotNull()
+ assertThat(authUI.auth.currentUser!!.email).isEqualTo(email)
+
+ // With isCredentialManagerEnabled=false, PasswordCredentialHandler won't be invoked
+ // Test passes if sign-up works without credential manager
+ println("TEST: With credential manager disabled, sign-up works correctly")
+ }
+
@Composable
private fun TestFirebaseAuthScreen(
configuration: AuthUIConfiguration,
diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt
index eab1c29d2..64103ec32 100644
--- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt
+++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt
@@ -119,6 +119,11 @@ class GoogleAuthScreenTest {
autoSelectEnabled = autoSelectEnabled
)
}
+
+ override suspend fun clearCredentialState(
+ context: Context,
+ credentialManager: CredentialManager,
+ ) {}
}
authUI.testCredentialManagerProvider = testCredentialManagerProvider