From fc7ef5bfc164222b5db1223bd9dcecec7cd2bcb3 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:22:10 -0800 Subject: [PATCH 1/4] add get and show password inputs to localization resolver, use for obsure text widget on authenticator forms --- .../l10n/generated/input_localizations.dart | 12 +++++++++ .../generated/input_localizations_en.dart | 6 +++++ .../lib/src/l10n/input_resolver.dart | 26 +++++++++++++++++++ .../lib/src/widgets/form.dart | 4 +++ 4 files changed, 48 insertions(+) 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/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), ); }, ); From f81873edab85161906f604f63027bca3637696b2 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:51:10 -0800 Subject: [PATCH 2/4] wrap materialbanner in semantics and place helpertext on InputDecoration --- .../src/mixins/authenticator_text_field.dart | 4 ++ .../lib/src/widgets/authenticator_banner.dart | 48 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) 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), + ), ), - ), - ], + ], + ), ), ); } From 1ae60f2e2170347b279493459a9b3ac1b28eae0b Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:22:10 -0800 Subject: [PATCH 3/4] add get and show password inputs to localization resolver, use for obsure text widget on authenticator forms --- .../l10n/generated/input_localizations.dart | 12 +++++++++ .../generated/input_localizations_en.dart | 6 +++++ .../lib/src/l10n/input_resolver.dart | 26 +++++++++++++++++++ .../lib/src/widgets/form.dart | 4 +++ 4 files changed, 48 insertions(+) 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/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), ); }, ); From 77ba01789e0ca874c87d1b5ec802af20415068e8 Mon Sep 17 00:00:00 2001 From: Ekjot <43255916+ekjotmultani@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:51:10 -0800 Subject: [PATCH 4/4] wrap materialbanner in semantics and place helpertext on InputDecoration --- .../src/mixins/authenticator_text_field.dart | 4 ++ .../lib/src/widgets/authenticator_banner.dart | 48 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) 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), + ), ), - ), - ], + ], + ), ), ); }