import 'package:shuffle/core/l10n/l10n.dart'; import 'package:shuffle/features/auth/domain/auth_failure.dart'; import 'package:shuffle/features/auth/domain/validation/password_policy.dart'; /// User-facing text of an [AuthFailure] (Spec #1 ยง 4.4, texts from /// `docs/design/auth-screens.md`). /// /// `emailSendLimited` has no text of its own: screens treat it as success /// (F12) or convert it to `rateLimited` beforehand. String authFailureMessage(AppLocalizations l10n, AuthFailure failure) => switch (failure) { InvalidCredentials() => l10n.authErrorInvalidCredentials, EmailNotConfirmed() => l10n.authErrorEmailNotConfirmed, EmailSendLimited() || RateLimited() => l10n.authErrorRateLimited, WeakPassword(:final reasons) => weakPasswordMessage(l10n, reasons), SamePassword() => l10n.authErrorSamePassword, LinkInvalidOrExpired() => l10n.authErrorLinkInvalidOrExpired, InvalidEmail() => l10n.authErrorEmailInvalid, RegistrationRejected() => l10n.authErrorRegistrationRejected, NetworkFailure() => l10n.authErrorNetwork, Unexpected() => l10n.authErrorUnexpected, }; /// One sentence per distinct reason; an empty list gets the generic text. String weakPasswordMessage( AppLocalizations l10n, List reasons, ) { final texts = { for (final reason in reasons) switch (reason) { WeakPasswordReason.length => l10n.authErrorWeakPasswordLength, WeakPasswordReason.pwned => l10n.authErrorWeakPasswordPwned, WeakPasswordReason.characters || WeakPasswordReason.unknown => l10n.authErrorWeakPasswordCharacters, }, }; return texts.isEmpty ? l10n.authErrorWeakPasswordCharacters : texts.join(' '); } /// Failures the user can fix by simply trying again (offline, unexpected). extension AuthFailureRetry on AuthFailure { bool get isRetryable => this is NetworkFailure || this is Unexpected; } /// Text of the password field error, shared by registration and reset. /// /// Order: server answer, `same_password`, "required", then the local policy /// [error] (only passed when it should be shown). String? passwordFieldError( AppLocalizations l10n, { required PasswordError? error, required bool required, List? serverReasons, bool serverSamePassword = false, }) { if (serverReasons != null) { return weakPasswordMessage(l10n, serverReasons); } if (serverSamePassword) { return l10n.authErrorSamePassword; } if (required) { return l10n.authPasswordRequired; } return switch (error) { PasswordTooShort(:final missing) => l10n.authPasswordTooShort(missing), PasswordCommon() => l10n.authPasswordCommon, null => null, }; } /// The only message shown is the "n missing" count, which changes with every /// keystroke: screens announce it only when the field is left. bool isPasswordLengthHintOnly({ required PasswordError? error, required bool required, List? serverReasons, bool serverSamePassword = false, }) => serverReasons == null && !serverSamePassword && !required && error is PasswordTooShort;