Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions auth/src/main/java/com/firebase/ui/auth/compose/ErrorRecoveryDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright 2025 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.firebase.ui.auth.compose

import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.window.DialogProperties
import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider

/**
* A composable dialog for displaying authentication errors with recovery options.
*
* This dialog provides friendly error messages and actionable recovery suggestions
* based on the specific [AuthException] type. It integrates with [AuthUIStringProvider]
* for localization support.
*
* **Example usage:**
* ```kotlin
* var showError by remember { mutableStateOf<AuthException?>(null) }
*
* if (showError != null) {
* ErrorRecoveryDialog(
* error = showError!!,
* stringProvider = stringProvider,
* onRetry = {
* showError = null
* // Retry authentication operation
* },
* onDismiss = {
* showError = null
* }
* )
* }
* ```
*
* @param error The [AuthException] to display recovery information for
* @param stringProvider The [AuthUIStringProvider] for localized strings
* @param onRetry Callback invoked when the user taps the retry action
* @param onDismiss Callback invoked when the user dismisses the dialog
* @param onRecover Optional callback for custom recovery actions based on the exception type
* @param modifier Optional [Modifier] for the dialog
* @param properties Optional [DialogProperties] for dialog configuration
*
* @since 10.0.0
*/
@Composable
fun ErrorRecoveryDialog(
error: AuthException,
stringProvider: AuthUIStringProvider,
onRetry: () -> Unit,
onDismiss: () -> Unit,
onRecover: ((AuthException) -> Unit)? = null,
modifier: Modifier = Modifier,
properties: DialogProperties = DialogProperties()
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringProvider.errorDialogTitle,
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Text(
text = getRecoveryMessage(error, stringProvider),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Start
)
},
confirmButton = {
if (isRecoverable(error)) {
TextButton(
onClick = {
onRecover?.invoke(error) ?: onRetry()
}
) {
Text(
text = getRecoveryActionText(error, stringProvider),
style = MaterialTheme.typography.labelLarge
)
}
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = stringProvider.dismissAction,
style = MaterialTheme.typography.labelLarge
)
}
},
modifier = modifier,
properties = properties
)
}

/**
* Gets the appropriate recovery message for the given [AuthException].
*
* @param error The [AuthException] to get the message for
* @param stringProvider The [AuthUIStringProvider] for localized strings
* @return The localized recovery message
*/
private fun getRecoveryMessage(
error: AuthException,
stringProvider: AuthUIStringProvider
): String {
return when (error) {
is AuthException.NetworkException -> stringProvider.networkErrorRecoveryMessage
is AuthException.InvalidCredentialsException -> stringProvider.invalidCredentialsRecoveryMessage
is AuthException.UserNotFoundException -> stringProvider.userNotFoundRecoveryMessage
is AuthException.WeakPasswordException -> {
// Include specific reason if available
val baseMessage = stringProvider.weakPasswordRecoveryMessage
error.reason?.let { reason ->
"$baseMessage\n\nReason: $reason"
} ?: baseMessage
}
is AuthException.EmailAlreadyInUseException -> {
// Include email if available
val baseMessage = stringProvider.emailAlreadyInUseRecoveryMessage
error.email?.let { email ->
"$baseMessage ($email)"
} ?: baseMessage
}
is AuthException.TooManyRequestsException -> stringProvider.tooManyRequestsRecoveryMessage
is AuthException.MfaRequiredException -> stringProvider.mfaRequiredRecoveryMessage
is AuthException.AccountLinkingRequiredException -> stringProvider.accountLinkingRequiredRecoveryMessage
is AuthException.AuthCancelledException -> stringProvider.authCancelledRecoveryMessage
is AuthException.UnknownException -> stringProvider.unknownErrorRecoveryMessage
else -> stringProvider.unknownErrorRecoveryMessage
}
}

/**
* Gets the appropriate recovery action text for the given [AuthException].
*
* @param error The [AuthException] to get the action text for
* @param stringProvider The [AuthUIStringProvider] for localized strings
* @return The localized action text
*/
private fun getRecoveryActionText(
error: AuthException,
stringProvider: AuthUIStringProvider
): String {
return when (error) {
is AuthException.AuthCancelledException -> stringProvider.continueText
is AuthException.EmailAlreadyInUseException -> stringProvider.signInDefault // Use existing "Sign in" text
is AuthException.AccountLinkingRequiredException -> stringProvider.continueText // Use "Continue" for linking
is AuthException.MfaRequiredException -> stringProvider.continueText // Use "Continue" for MFA
is AuthException.NetworkException,
is AuthException.InvalidCredentialsException,
is AuthException.UserNotFoundException,
is AuthException.WeakPasswordException,
is AuthException.TooManyRequestsException,
is AuthException.UnknownException -> stringProvider.retryAction
else -> stringProvider.retryAction
}
}

/**
* Determines if the given [AuthException] is recoverable through user action.
*
* @param error The [AuthException] to check
* @return `true` if the error is recoverable, `false` otherwise
*/
private fun isRecoverable(error: AuthException): Boolean {
return when (error) {
is AuthException.NetworkException -> true
is AuthException.InvalidCredentialsException -> true
is AuthException.UserNotFoundException -> true
is AuthException.WeakPasswordException -> true
is AuthException.EmailAlreadyInUseException -> true
is AuthException.TooManyRequestsException -> false // User must wait
is AuthException.MfaRequiredException -> true
is AuthException.AccountLinkingRequiredException -> true
is AuthException.AuthCancelledException -> true
is AuthException.UnknownException -> true
else -> true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,44 @@ interface AuthUIStringProvider {

/** TOTP Code prompt */
val enterTOTPCode: String

// Error Recovery Dialog Strings
/** Error dialog title */
val errorDialogTitle: String

/** Retry action button text */
val retryAction: String

/** Dismiss action button text */
val dismissAction: String

/** Network error recovery message */
val networkErrorRecoveryMessage: String

/** Invalid credentials recovery message */
val invalidCredentialsRecoveryMessage: String

/** User not found recovery message */
val userNotFoundRecoveryMessage: String

/** Weak password recovery message */
val weakPasswordRecoveryMessage: String

/** Email already in use recovery message */
val emailAlreadyInUseRecoveryMessage: String

/** Too many requests recovery message */
val tooManyRequestsRecoveryMessage: String

/** MFA required recovery message */
val mfaRequiredRecoveryMessage: String

/** Account linking required recovery message */
val accountLinkingRequiredRecoveryMessage: String

/** Auth cancelled recovery message */
val authCancelledRecoveryMessage: String

/** Unknown error recovery message */
val unknownErrorRecoveryMessage: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,34 @@ class DefaultAuthUIStringProvider(
get() = localizedContext.getString(R.string.fui_progress_dialog_loading)
override val noInternet: String
get() = localizedContext.getString(R.string.fui_no_internet)

/**
* Error Recovery Dialog Strings
*/
override val errorDialogTitle: String
get() = localizedContext.getString(R.string.fui_error_dialog_title)
override val retryAction: String
get() = localizedContext.getString(R.string.fui_error_retry_action)
override val dismissAction: String
get() = localizedContext.getString(R.string.fui_email_link_dismiss_button)
override val networkErrorRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_no_internet)
override val invalidCredentialsRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_invalid_password)
override val userNotFoundRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_email_does_not_exist)
override val weakPasswordRecoveryMessage: String
get() = localizedContext.resources.getQuantityString(R.plurals.fui_error_weak_password, 6, 6)
override val emailAlreadyInUseRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_email_account_creation_error)
override val tooManyRequestsRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_too_many_attempts)
override val mfaRequiredRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_mfa_required_message)
override val accountLinkingRequiredRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_account_linking_required_message)
override val authCancelledRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_auth_cancelled_message)
override val unknownErrorRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_unknown)
}
7 changes: 6 additions & 1 deletion auth/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,10 @@
<string name="fui_resend_code">إعادة إرسال الرمز</string>
<string name="fui_verify_phone_number">تأكيد ملكية رقم الهاتف</string>
<string name="fui_sms_terms_of_service">عند النقر على “%1$s”، قد يتمّ إرسال رسالة قصيرة SMS وقد يتمّ تطبيق رسوم الرسائل والبيانات.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">يشير النقر على “%1$s” إلى موافقتك على %2$s و%3$s. وقد يتمّ إرسال رسالة قصيرة كما قد تنطبق رسوم الرسائل والبيانات.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">يشير النقر على "%1$s" إلى موافقتك على %2$s و%3$s. وقد يتمّ إرسال رسالة قصيرة كما قد تنطبق رسوم الرسائل والبيانات.</string>
<string name="fui_error_dialog_title" translation_description="Title for error recovery dialog">خطأ في المصادقة</string>
<string name="fui_error_retry_action" translation_description="Retry button text in error dialog">أعد المحاولة</string>
<string name="fui_error_mfa_required_message" translation_description="Error message when MFA is required">مطلوب تحقق إضافي. يرجى إكمال المصادقة متعددة العوامل.</string>
<string name="fui_error_account_linking_required_message" translation_description="Error message when account linking is required">يجب ربط الحساب. جرب طريقة تسجيل دخول مختلفة.</string>
<string name="fui_error_auth_cancelled_message" translation_description="Error message when authentication is cancelled">تم إلغاء المصادقة. أعد المحاولة عندما تكون جاهزاً.</string>
</resources>
7 changes: 6 additions & 1 deletion auth/src/main/res/values-b+es+419/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,10 @@
<string name="fui_resend_code">Reenviar código</string>
<string name="fui_verify_phone_number">Verificar número de teléfono</string>
<string name="fui_sms_terms_of_service">Si presionas “%1$s”, se enviará un SMS. Se aplicarán las tarifas de mensajes y datos.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">Si presionas “%1$s”, indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">Si presionas "%1$s", indicas que aceptas nuestras %2$s y %3$s. Es posible que se te envíe un SMS. Podrían aplicarse las tarifas de mensajes y datos.</string>
<string name="fui_error_dialog_title" translation_description="Title for error recovery dialog">Error de autenticación</string>
<string name="fui_error_retry_action" translation_description="Retry button text in error dialog">Intentar de nuevo</string>
<string name="fui_error_mfa_required_message" translation_description="Error message when MFA is required">Se requiere verificación adicional. Complete la autenticación de múltiples factores.</string>
<string name="fui_error_account_linking_required_message" translation_description="Error message when account linking is required">Es necesario vincular la cuenta. Pruebe con un método de inicio de sesión diferente.</string>
<string name="fui_error_auth_cancelled_message" translation_description="Error message when authentication is cancelled">La autenticación fue cancelada. Vuelva a intentarlo cuando esté listo.</string>
</resources>
7 changes: 6 additions & 1 deletion auth/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,10 @@
<string name="fui_resend_code">Повторно изпращане на кода</string>
<string name="fui_verify_phone_number">Потвърждаване на телефонния номер</string>
<string name="fui_sms_terms_of_service">Докосвайки „%1$s“, може да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">Докосвайки „%1$s“, приемате нашите %2$s и %3$s. Възможно е да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">Докосвайки „%1$s", приемате нашите %2$s и %3$s. Възможно е да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS.</string>
<string name="fui_error_dialog_title" translation_description="Title for error recovery dialog">Грешка при удостоверяване</string>
<string name="fui_error_retry_action" translation_description="Retry button text in error dialog">Опитай отново</string>
<string name="fui_error_mfa_required_message" translation_description="Error message when MFA is required">Необходима е допълнителна проверка. Моля, завършете многофакторното удостоверяване.</string>
<string name="fui_error_account_linking_required_message" translation_description="Error message when account linking is required">Акаунтът трябва да бъде свързан. Опитайте различен метод за влизане.</string>
<string name="fui_error_auth_cancelled_message" translation_description="Error message when authentication is cancelled">Удостоверяването беше отменено. Опитайте отново, когато сте готови.</string>
</resources>
6 changes: 6 additions & 0 deletions auth/src/main/res/values-bn/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,10 @@
<string name="fui_verify_phone_number">ফোন নম্বর যাচাই করুন</string>
<string name="fui_sms_terms_of_service">%1$s এ ট্যাপ করলে আপনি একটি এসএমএস পাঠাতে পারেন। মেসেজ ও ডেটার চার্জ প্রযোজ্য।</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">“%1$s” বোতামে ট্যাপ করার অর্থ, আপনি আমাদের %2$s এবং %3$s-এর সাথে সম্মত। একটি এসএমএস পাঠানো হতে পারে। মেসেজ এবং ডেটার উপরে প্রযোজ্য চার্জ লাগতে পারে।</string>

<string name="fui_error_dialog_title" translation_description="Title for error recovery dialog">Authentication Error</string>
<string name="fui_error_retry_action" translation_description="Retry button text in error dialog">Try again</string>
<string name="fui_error_mfa_required_message" translation_description="Error message when MFA is required">Additional verification required. Please complete multi-factor authentication.</string>
<string name="fui_error_account_linking_required_message" translation_description="Error message when account linking is required">Account needs to be linked. Please try a different sign-in method.</string>
<string name="fui_error_auth_cancelled_message" translation_description="Error message when authentication is cancelled">Authentication was cancelled. Please try again when ready.</string>
</resources>
6 changes: 6 additions & 0 deletions auth/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,10 @@
<string name="fui_verify_phone_number">Verifica el número de telèfon</string>
<string name="fui_sms_terms_of_service">En tocar %1$s, és possible que s\'enviï un SMS. Es poden aplicar tarifes de dades i missatges.</string>
<string name="fui_sms_terms_of_service_and_privacy_policy_extended">En tocar %1$s, acceptes les nostres %2$s i la nostra %3$s. És possible que s\'enviï un SMS. Es poden aplicar tarifes de dades i missatges.</string>

<string name="fui_error_dialog_title" translation_description="Title for error recovery dialog">Authentication Error</string>
<string name="fui_error_retry_action" translation_description="Retry button text in error dialog">Try again</string>
<string name="fui_error_mfa_required_message" translation_description="Error message when MFA is required">Additional verification required. Please complete multi-factor authentication.</string>
<string name="fui_error_account_linking_required_message" translation_description="Error message when account linking is required">Account needs to be linked. Please try a different sign-in method.</string>
<string name="fui_error_auth_cancelled_message" translation_description="Error message when authentication is cancelled">Authentication was cancelled. Please try again when ready.</string>
</resources>
Loading