diff --git a/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations.dart b/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations.dart index 7755033b5f2..e4313efdb0f 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations.dart @@ -306,6 +306,18 @@ abstract class AuthenticatorInputLocalizations { /// In en, this message translates to: /// **'Please enter the code from your registered Authenticator app'** String get totpCodePrompt; + + /// Tooltip for the button to show the password when it is currently hidden. + /// + /// In en, this message translates to: + /// **'Show password'** + String get showPassword; + + /// Tooltip for the button to hide the password when it is currently visible. + /// + /// In en, this message translates to: + /// **'Hide password'** + String get hidePassword; } class _AuthenticatorInputLocalizationsDelegate diff --git a/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations_en.dart b/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations_en.dart index beef6169605..8fcdd3a15e7 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations_en.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/l10n/generated/input_localizations_en.dart @@ -153,4 +153,10 @@ class AuthenticatorInputLocalizationsEn @override String get totpCodePrompt => 'Please enter the code from your registered Authenticator app'; + + @override + String get showPassword => 'Show password'; + + @override + String get hidePassword => 'Hide password'; } diff --git a/packages/authenticator/amplify_authenticator/lib/src/l10n/input_resolver.dart b/packages/authenticator/amplify_authenticator/lib/src/l10n/input_resolver.dart index 81c4b51cff8..a23bc712e09 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/l10n/input_resolver.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/l10n/input_resolver.dart @@ -47,6 +47,8 @@ enum InputResolverKeyType { passwordRequirements, format, mismatch, + showPasswordTooltip, + hidePasswordTooltip, } class InputResolverKey { @@ -388,6 +390,16 @@ class InputResolverKey { field: InputField.usernameType, ); + static const showPasswordTooltip = InputResolverKey._( + InputResolverKeyType.showPasswordTooltip, + field: InputField.password, + ); + + static const hidePasswordTooltip = InputResolverKey._( + InputResolverKeyType.hidePasswordTooltip, + field: InputField.password, + ); + String resolve(BuildContext context, InputResolver inputResolver) => inputResolver.resolve(context, this); } @@ -552,6 +564,16 @@ class InputResolver extends Resolver { return AuthenticatorLocalizations.inputsOf(context).optional(title); } + /// Returns the tooltip text for showing a hidden password. + String showPasswordTooltip(BuildContext context) { + return AuthenticatorLocalizations.inputsOf(context).showPassword; + } + + /// Returns the tooltip text for hiding a visible password. + String hidePasswordTooltip(BuildContext context) { + return AuthenticatorLocalizations.inputsOf(context).hidePassword; + } + @override String resolve(BuildContext context, InputResolverKey key) { switch (key.type) { @@ -571,6 +593,10 @@ class InputResolver extends Resolver { return AuthenticatorLocalizations.inputsOf(context).passwordsDoNotMatch; case InputResolverKeyType.format: return format(context, key.field); + case InputResolverKeyType.showPasswordTooltip: + return showPasswordTooltip(context); + case InputResolverKeyType.hidePasswordTooltip: + return hidePasswordTooltip(context); } } } diff --git a/packages/authenticator/amplify_authenticator/lib/src/mixins/authenticator_text_field.dart b/packages/authenticator/amplify_authenticator/lib/src/mixins/authenticator_text_field.dart index 1d69b9d985d..20fbca48a0c 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/mixins/authenticator_text_field.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/mixins/authenticator_text_field.dart @@ -204,6 +204,10 @@ mixin AuthenticatorTextField< suffixIcon: suffix, errorMaxLines: errorMaxLines, hintText: hintText, + // Workaround for Flutter issue where validation errors are not + // announced by screen readers on web unless helperText is set. + // See: https://github.com/flutter/flutter/issues/99715 + helperText: ' ', ), maxLength: maxLength, maxLengthEnforcement: MaxLengthEnforcement.enforced, diff --git a/packages/authenticator/amplify_authenticator/lib/src/widgets/authenticator_banner.dart b/packages/authenticator/amplify_authenticator/lib/src/widgets/authenticator_banner.dart index 7e1a4a57b6b..5050f50614e 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/widgets/authenticator_banner.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/widgets/authenticator_banner.dart @@ -25,17 +25,20 @@ MaterialBanner createMaterialBanner( key: keyAuthenticatorBanner, backgroundColor: colorsChoices.background, leading: Icon(type.icon, color: colorsChoices.foreground), - content: Center( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - message.trim(), - style: TextStyle(color: colorsChoices.foreground), + content: Semantics( + liveRegion: true, + child: Center( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + message.trim(), + style: TextStyle(color: colorsChoices.foreground), + ), ), - ), - ], + ], + ), ), ), actions: [ @@ -70,18 +73,21 @@ SnackBar createSnackBar( return SnackBar( key: keyAuthenticatorBanner, backgroundColor: colorsChoices.background, - content: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(type.icon, color: fallbackIconColor), - const SizedBox(width: 16), - Expanded( - child: Text( - message.trim(), - style: TextStyle(color: colorsChoices.foreground), + content: Semantics( + liveRegion: true, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(type.icon, color: fallbackIconColor), + const SizedBox(width: 16), + Expanded( + child: Text( + message.trim(), + style: TextStyle(color: colorsChoices.foreground), + ), ), - ), - ], + ], + ), ), ); } diff --git a/packages/authenticator/amplify_authenticator/lib/src/widgets/form.dart b/packages/authenticator/amplify_authenticator/lib/src/widgets/form.dart index 25bbb022a38..49147d609ab 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/widgets/form.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/widgets/form.dart @@ -145,6 +145,7 @@ class AuthenticatorFormState return ValueListenableBuilder( valueListenable: obscureTextToggleValue, builder: (BuildContext context, bool toggleObscureText, Widget? _) { + final inputResolver = stringResolver.inputs; return IconButton( onPressed: () { obscureTextToggleValue.value = !toggleObscureText; @@ -152,6 +153,9 @@ class AuthenticatorFormState icon: Icon( toggleObscureText ? Icons.visibility : Icons.visibility_off, ), + tooltip: toggleObscureText + ? inputResolver.showPasswordTooltip(context) + : inputResolver.hidePasswordTooltip(context), ); }, );