Loading app/src/main/java/foundation/e/apps/feature/auth/login/LoginFailureMessageResolver.kt +19 −9 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package foundation.e.apps.feature.auth.login import android.content.Context import androidx.annotation.StringRes import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.domain.auth.AuthError import javax.inject.Inject Loading @@ -36,20 +37,29 @@ class LoginFailureMessageResolver @Inject constructor( ): String { val fallbackMessage = fallbackMessage(fallbackMessageRes) return when (failure) { is LoginWorkflowFailure.RefreshValidationFailed -> failure.authError?.messageOrNull() ?: fallbackMessage is LoginWorkflowFailure.RefreshValidationFailed -> { failure.authError?.messageRes()?.let { context.getString(it) } ?: fallbackMessage } is LoginWorkflowFailure.SubmissionFailed -> fallbackMessage is LoginWorkflowFailure.RollbackFailed -> context.getString(R.string.sign_in_recovery_failed) } } private fun AuthError.messageOrNull(): String? { @StringRes private fun AuthError.messageRes(): Int? { return when (this) { is AuthError.InvalidToken -> message is AuthError.LoginRequired -> message is AuthError.NetworkFailure -> message is AuthError.RateLimited -> message is AuthError.Unknown -> message is AuthError.InvalidToken -> R.string.sign_in_session_invalid is AuthError.LoginRequired -> R.string.sign_in_login_required is AuthError.NetworkFailure -> if (isTimeout) { R.string.sign_in_network_timeout } else { R.string.sign_in_network_failure } is AuthError.RateLimited -> R.string.sign_in_rate_limited is AuthError.Unknown -> null } } } app/src/main/java/foundation/e/apps/feature/auth/login/LoginWorkflowCoordinator.kt +6 −16 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import foundation.e.apps.domain.auth.AuthSession import foundation.e.apps.domain.auth.AuthSessionRepository import foundation.e.apps.domain.auth.AuthStore import foundation.e.apps.domain.auth.PlayStoreLoginMode import foundation.e.apps.domain.auth.describe import foundation.e.apps.domain.login.AnonymousLoginUseCase import foundation.e.apps.domain.login.GoogleLoginUseCase import foundation.e.apps.domain.login.MicrogLoginUseCase Loading Loading @@ -265,8 +264,8 @@ class LoginWorkflowCoordinator @Inject constructor( } catch (exception: CancellationException) { throw exception } catch (exception: Exception) { // Rollback must report any unexpected non-cancellation failure as a submission // failure rather than letting it escape and leave the flow in an inconsistent state. // Rollback must report any unexpected non-cancellation failure as a typed // workflow failure rather than letting it escape and leave the flow inconsistent. IllegalStateException("Failed to rollback login after $rollbackContext", exception) } Loading @@ -275,22 +274,13 @@ class LoginWorkflowCoordinator @Inject constructor( } Timber.e(rollbackFailure, "Login rollback failed after %s", rollbackContext) rollbackFailure.addSuppressed(failure.toThrowable()) return LoginWorkflowResult.Failure( LoginWorkflowFailure.SubmissionFailed(rollbackFailure) LoginWorkflowFailure.RollbackFailed( originalFailure = failure, rollbackThrowable = rollbackFailure, ) } private fun LoginWorkflowFailure.toThrowable(): Throwable { return when (this) { is LoginWorkflowFailure.SubmissionFailed -> throwable is LoginWorkflowFailure.RefreshValidationFailed -> IllegalStateException( "Original login refresh validation failed for $preferredStore: " + (authError?.describe() ?: "unknown auth error") ) } } private suspend fun captureRollbackState(): LoginRollbackState { return LoginRollbackState( Loading app/src/main/java/foundation/e/apps/feature/auth/login/LoginWorkflowResult.kt +5 −0 Original line number Diff line number Diff line Loading @@ -37,4 +37,9 @@ sealed interface LoginWorkflowFailure { val preferredStore: AuthStore, val authError: AuthError?, ) : LoginWorkflowFailure data class RollbackFailed( val originalFailure: LoginWorkflowFailure, val rollbackThrowable: Throwable, ) : LoginWorkflowFailure } app/src/main/java/foundation/e/apps/feature/auth/login/SystemWideGoogleLoginCoordinator.kt +17 −3 Original line number Diff line number Diff line Loading @@ -17,6 +17,20 @@ package foundation.e.apps.feature.auth.login import androidx.annotation.StringRes import foundation.e.apps.R internal enum class SystemWideGoogleLoginFailureReason { AccountFetchFailed, } @StringRes internal fun SystemWideGoogleLoginFailureReason.messageRes(): Int { return when (this) { SystemWideGoogleLoginFailureReason.AccountFetchFailed -> R.string.sign_in_microg_login_failed } } internal class SystemWideGoogleLoginCoordinator { private var pendingAccountName: String? = null Loading Loading @@ -71,8 +85,8 @@ internal class SystemWideGoogleLoginCoordinator { return Action.LaunchConsent } fun onFetchFailure(message: String?): Action { return Action.FetchFailed(message) fun onFetchFailure(): Action { return Action.FetchFailed(SystemWideGoogleLoginFailureReason.AccountFetchFailed) } internal sealed interface Action { Loading @@ -94,7 +108,7 @@ internal class SystemWideGoogleLoginCoordinator { ) : Action data class FetchFailed( val message: String?, val reason: SystemWideGoogleLoginFailureReason, ) : Action } } app/src/main/java/foundation/e/apps/feature/auth/login/SystemWideGoogleLoginLauncher.kt +2 −5 Original line number Diff line number Diff line Loading @@ -109,7 +109,7 @@ class SystemWideGoogleLoginLauncher( is MicrogAccountFetchResult.Error -> { pendingConsentIntent = null handleAction(coordinator.onFetchFailure(fetchResult.throwable.localizedMessage)) handleAction(coordinator.onFetchFailure()) } } } Loading Loading @@ -183,10 +183,7 @@ class SystemWideGoogleLoginLauncher( } is SystemWideGoogleLoginCoordinator.Action.FetchFailed -> { onError( action.message ?: fragment.getString(R.string.sign_in_microg_login_failed), ) onError(fragment.getString(action.reason.messageRes())) } } } Loading Loading
app/src/main/java/foundation/e/apps/feature/auth/login/LoginFailureMessageResolver.kt +19 −9 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package foundation.e.apps.feature.auth.login import android.content.Context import androidx.annotation.StringRes import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R import foundation.e.apps.domain.auth.AuthError import javax.inject.Inject Loading @@ -36,20 +37,29 @@ class LoginFailureMessageResolver @Inject constructor( ): String { val fallbackMessage = fallbackMessage(fallbackMessageRes) return when (failure) { is LoginWorkflowFailure.RefreshValidationFailed -> failure.authError?.messageOrNull() ?: fallbackMessage is LoginWorkflowFailure.RefreshValidationFailed -> { failure.authError?.messageRes()?.let { context.getString(it) } ?: fallbackMessage } is LoginWorkflowFailure.SubmissionFailed -> fallbackMessage is LoginWorkflowFailure.RollbackFailed -> context.getString(R.string.sign_in_recovery_failed) } } private fun AuthError.messageOrNull(): String? { @StringRes private fun AuthError.messageRes(): Int? { return when (this) { is AuthError.InvalidToken -> message is AuthError.LoginRequired -> message is AuthError.NetworkFailure -> message is AuthError.RateLimited -> message is AuthError.Unknown -> message is AuthError.InvalidToken -> R.string.sign_in_session_invalid is AuthError.LoginRequired -> R.string.sign_in_login_required is AuthError.NetworkFailure -> if (isTimeout) { R.string.sign_in_network_timeout } else { R.string.sign_in_network_failure } is AuthError.RateLimited -> R.string.sign_in_rate_limited is AuthError.Unknown -> null } } }
app/src/main/java/foundation/e/apps/feature/auth/login/LoginWorkflowCoordinator.kt +6 −16 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import foundation.e.apps.domain.auth.AuthSession import foundation.e.apps.domain.auth.AuthSessionRepository import foundation.e.apps.domain.auth.AuthStore import foundation.e.apps.domain.auth.PlayStoreLoginMode import foundation.e.apps.domain.auth.describe import foundation.e.apps.domain.login.AnonymousLoginUseCase import foundation.e.apps.domain.login.GoogleLoginUseCase import foundation.e.apps.domain.login.MicrogLoginUseCase Loading Loading @@ -265,8 +264,8 @@ class LoginWorkflowCoordinator @Inject constructor( } catch (exception: CancellationException) { throw exception } catch (exception: Exception) { // Rollback must report any unexpected non-cancellation failure as a submission // failure rather than letting it escape and leave the flow in an inconsistent state. // Rollback must report any unexpected non-cancellation failure as a typed // workflow failure rather than letting it escape and leave the flow inconsistent. IllegalStateException("Failed to rollback login after $rollbackContext", exception) } Loading @@ -275,22 +274,13 @@ class LoginWorkflowCoordinator @Inject constructor( } Timber.e(rollbackFailure, "Login rollback failed after %s", rollbackContext) rollbackFailure.addSuppressed(failure.toThrowable()) return LoginWorkflowResult.Failure( LoginWorkflowFailure.SubmissionFailed(rollbackFailure) LoginWorkflowFailure.RollbackFailed( originalFailure = failure, rollbackThrowable = rollbackFailure, ) } private fun LoginWorkflowFailure.toThrowable(): Throwable { return when (this) { is LoginWorkflowFailure.SubmissionFailed -> throwable is LoginWorkflowFailure.RefreshValidationFailed -> IllegalStateException( "Original login refresh validation failed for $preferredStore: " + (authError?.describe() ?: "unknown auth error") ) } } private suspend fun captureRollbackState(): LoginRollbackState { return LoginRollbackState( Loading
app/src/main/java/foundation/e/apps/feature/auth/login/LoginWorkflowResult.kt +5 −0 Original line number Diff line number Diff line Loading @@ -37,4 +37,9 @@ sealed interface LoginWorkflowFailure { val preferredStore: AuthStore, val authError: AuthError?, ) : LoginWorkflowFailure data class RollbackFailed( val originalFailure: LoginWorkflowFailure, val rollbackThrowable: Throwable, ) : LoginWorkflowFailure }
app/src/main/java/foundation/e/apps/feature/auth/login/SystemWideGoogleLoginCoordinator.kt +17 −3 Original line number Diff line number Diff line Loading @@ -17,6 +17,20 @@ package foundation.e.apps.feature.auth.login import androidx.annotation.StringRes import foundation.e.apps.R internal enum class SystemWideGoogleLoginFailureReason { AccountFetchFailed, } @StringRes internal fun SystemWideGoogleLoginFailureReason.messageRes(): Int { return when (this) { SystemWideGoogleLoginFailureReason.AccountFetchFailed -> R.string.sign_in_microg_login_failed } } internal class SystemWideGoogleLoginCoordinator { private var pendingAccountName: String? = null Loading Loading @@ -71,8 +85,8 @@ internal class SystemWideGoogleLoginCoordinator { return Action.LaunchConsent } fun onFetchFailure(message: String?): Action { return Action.FetchFailed(message) fun onFetchFailure(): Action { return Action.FetchFailed(SystemWideGoogleLoginFailureReason.AccountFetchFailed) } internal sealed interface Action { Loading @@ -94,7 +108,7 @@ internal class SystemWideGoogleLoginCoordinator { ) : Action data class FetchFailed( val message: String?, val reason: SystemWideGoogleLoginFailureReason, ) : Action } }
app/src/main/java/foundation/e/apps/feature/auth/login/SystemWideGoogleLoginLauncher.kt +2 −5 Original line number Diff line number Diff line Loading @@ -109,7 +109,7 @@ class SystemWideGoogleLoginLauncher( is MicrogAccountFetchResult.Error -> { pendingConsentIntent = null handleAction(coordinator.onFetchFailure(fetchResult.throwable.localizedMessage)) handleAction(coordinator.onFetchFailure()) } } } Loading Loading @@ -183,10 +183,7 @@ class SystemWideGoogleLoginLauncher( } is SystemWideGoogleLoginCoordinator.Action.FetchFailed -> { onError( action.message ?: fragment.getString(R.string.sign_in_microg_login_failed), ) onError(fragment.getString(action.reason.messageRes())) } } } Loading