From 36a0c1faa0b47d4f735a79daf67c9e2c0089938e Mon Sep 17 00:00:00 2001 From: ernest-deriv <120568427+ernest-deriv@users.noreply.github.com> Date: Wed, 29 Nov 2023 06:54:37 +0400 Subject: [PATCH] refactor(deriv_auth_ui): [MOBC-629] Adding semantics to UI components (#321) * chore(deriv_auth_ui):Adding semantics to UI components * chore(deriv_auth_ui):Adding semantics to UI components, fixes based on review comments * chore(deriv_auth_ui):Adding explicitChildNodes: true to Semantics Widgets --- .../lib/src/core/helpers/semantic_labels.dart | 41 +++++++ .../layouts/deriv_get_started_layout.dart | 41 ++++--- .../login/layouts/deriv_login_layout.dart | 3 + .../layouts/deriv_reset_pass_layout.dart | 50 ++++---- .../signup/layouts/deriv_signup_layout.dart | 43 ++++--- .../presentation/widgets/base_text_field.dart | 113 ++++++++++-------- 6 files changed, 182 insertions(+), 109 deletions(-) create mode 100644 packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart diff --git a/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart b/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart new file mode 100644 index 000000000..b0f7e49df --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart @@ -0,0 +1,41 @@ +/// A class that contains semantic labels for various UI elements in the auth UI flow. +class SemanticsLabels { + // Signup Page + + /// A semantic label for the email field on the signup page. + static String signupEmailFieldSemantic = 'signup_email_field_semantic'; + + /// A semantic label for the signup button on the signup page. + static String signupButtonSemantic = 'signup_button_semantic'; + + /// A semantic label for the referral text field on the signup page. + static String signupReferralTextFieldSemantic = + 'signup_referral_field_semantic'; + + // Login Page + + /// A semantic label for the email field on the login page. + static String loginEmailFieldSemantic = 'login_email_field_semantic'; + + /// A semantic label for the password field on the login page. + static String loginPasswordFieldSemantic = 'login_password_field_semantic'; + + // Reset Password Page + + /// A semantic label for the email field on the reset password page. + static String resetPasswordEmailFieldSemantic = + 'reset_password_email_field_semantic'; + + /// A semantic label for the reset password button on the reset password page. + static String resetPasswordButtonSemantic = 'reset_password_button_semantic'; + + // Starter Page + + /// A semantic label for the signup button on the starter page. + static String starterPageSignupButtonSemantic = + 'starter_page_signup_button_semantic'; + + /// A semantic label for the login button on the starter page. + static String starterPageLoginButtonSemantic = + 'starter_page_login_button_semantic'; +} diff --git a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart b/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart index c074202e0..de6ce0670 100644 --- a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:math' as math; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_auth_ui/src/features/get_started/models/deriv_get_started_slide_model.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; @@ -139,26 +140,34 @@ class _DerivGetStartedLayoutState extends State { Widget _buildButtons() => Column( mainAxisSize: MainAxisSize.min, children: [ - PrimaryButton( - onPressed: widget.onSignupTapped, - child: Center( - child: Text( - context.localization.actionGetAFreeAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageSignupButtonSemantic, + child: PrimaryButton( + onPressed: widget.onSignupTapped, + child: Center( + child: Text( + context.localization.actionGetAFreeAccount, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), ), - SecondaryButton( - onPressed: widget.onLoginTapped, - child: Center( - child: Text( - context.localization.actionLogin, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageLoginButtonSemantic, + child: SecondaryButton( + onPressed: widget.onLoginTapped, + child: Center( + child: Text( + context.localization.actionLogin, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), diff --git a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart index 0bdb7ae3d..9671a72a8 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart @@ -4,6 +4,7 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; import 'package:deriv_theme/deriv_theme.dart'; @@ -158,6 +159,7 @@ class _DerivLoginLayoutState extends State { List _buildTextFields({required bool isEnabled}) => [ BaseTextField( + semanticLabel: SemanticsLabels.loginEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, labelText: context.localization.labelEmail, @@ -172,6 +174,7 @@ class _DerivLoginLayoutState extends State { ), const SizedBox(height: ThemeProvider.margin32), BaseTextField( + semanticLabel: SemanticsLabels.loginPasswordFieldSemantic, controller: _passwordController, focusNode: _passwordFocusNode, labelText: context.localization.labelPassword, diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart index 1182ead53..0a4ee0159 100644 --- a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart @@ -4,6 +4,7 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -135,6 +136,7 @@ class _DerivResetPassLayoutState extends State { ), const SizedBox(height: ThemeProvider.margin24), BaseTextField( + semanticLabel: SemanticsLabels.resetPasswordEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, labelText: context.localization.labelEmail, @@ -152,32 +154,36 @@ class _DerivResetPassLayoutState extends State { ), ); - Widget _buildSubmitEmailButton() => ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - context.theme.colors.coral.withOpacity( - getOpacity(isEnabled: _isFormValid()), + Widget _buildSubmitEmailButton() => Semantics( + explicitChildNodes: true, + label: SemanticsLabels.resetPasswordButtonSemantic, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + context.theme.colors.coral.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), - onPressed: _isFormValid() ? _onSubmitEmailTapped : null, - child: Center( - child: _isBusy - ? LoadingIndicator( - valueColor: context.theme.colors.prominent, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.margin16, - width: ThemeProvider.margin16, - ) - : Text( - context.localization.actionResetPass, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent.withOpacity( - getOpacity(isEnabled: _isFormValid()), + onPressed: _isFormValid() ? _onSubmitEmailTapped : null, + child: Center( + child: _isBusy + ? LoadingIndicator( + valueColor: context.theme.colors.prominent, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.margin16, + width: ThemeProvider.margin16, + ) + : Text( + context.localization.actionResetPass, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), + ), ), ); diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart index 2a34b8396..6bce2b9c8 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart @@ -1,6 +1,7 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_auth_ui/src/core/states/auth_state_listener.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; @@ -212,6 +213,8 @@ class _DerivSignupLayoutState extends State { vertical: ThemeProvider.margin16, ), child: BaseTextField( + semanticLabel: + SemanticsLabels.signupReferralTextFieldSemantic, controller: referralController, onChanged: (_) { if (mounted) { @@ -276,6 +279,7 @@ class _DerivSignupLayoutState extends State { ); Widget _buildEmailTextField() => BaseTextField( + semanticLabel: SemanticsLabels.signupEmailFieldSemantic, controller: emailController, focusNode: emailFocusNode, labelText: context.localization.labelEmail, @@ -291,25 +295,28 @@ class _DerivSignupLayoutState extends State { }, ); - Widget _buildSignUpButton() => PrimaryButton( - isEnabled: _isFormValid(), - onPressed: _onSignupTapped, - child: Center( - child: - context.read().state is DerivSignupProgressState - ? const LoadingIndicator( - valueColor: Colors.white, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.iconSize16, - width: ThemeProvider.iconSize16, - ) - : Text( - context.localization.actionCreateAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, - ), + Widget _buildSignUpButton() => Semantics( + label: SemanticsLabels.signupButtonSemantic, + child: PrimaryButton( + isEnabled: _isFormValid(), + onPressed: _onSignupTapped, + child: Center( + child: context.read().state + is DerivSignupProgressState + ? const LoadingIndicator( + valueColor: Colors.white, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + ) + : Text( + context.localization.actionCreateAccount, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, ), + ), + ), ), ); diff --git a/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart b/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart index 60a9ee1c9..7aeeff8e6 100644 --- a/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart +++ b/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart @@ -32,6 +32,7 @@ class BaseTextField extends StatefulWidget { this.onEditingComplete, this.onChanged, this.onTap, + this.semanticLabel, Key? key, }) : super(key: key); @@ -113,6 +114,9 @@ class BaseTextField extends StatefulWidget { /// onTap callback function. final VoidCallback? onTap; + /// Semantic label. + final String? semanticLabel; + @override _BaseTextFieldState createState() => _BaseTextFieldState(); } @@ -132,63 +136,66 @@ class _BaseTextFieldState extends State { } @override - Widget build(BuildContext context) => TextFormField( - key: _formFieldKey, - controller: widget.controller, - focusNode: widget.focusNode, - initialValue: widget.initialValue, - keyboardType: widget.keyboardType, - maxLength: widget.maxLength, - autocorrect: false, - readOnly: widget.readOnly, - enabled: widget.enabled, - obscureText: widget.obscureText, - textInputAction: widget.textInputAction, - inputFormatters: widget.inputFormatters, - decoration: InputDecoration( - labelText: widget.labelText, - border: const OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: widget.borderColor ?? context.theme.colors.hover, + Widget build(BuildContext context) => Semantics( + explicitChildNodes: true, + label: widget.semanticLabel, + child: TextFormField( + key: _formFieldKey, + controller: widget.controller, + focusNode: widget.focusNode, + initialValue: widget.initialValue, + keyboardType: widget.keyboardType, + maxLength: widget.maxLength, + autocorrect: false, + readOnly: widget.readOnly, + enabled: widget.enabled, + obscureText: widget.obscureText, + textInputAction: widget.textInputAction, + inputFormatters: widget.inputFormatters, + decoration: InputDecoration( + labelText: widget.labelText, + border: const OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: widget.borderColor ?? context.theme.colors.hover, + ), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), ), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: widget.focusedBorderColor ?? context.theme.colors.blue, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: widget.focusedBorderColor ?? context.theme.colors.blue, + ), ), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.theme.colors.coral), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.theme.colors.coral), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - labelStyle: context.theme.textStyle( - textStyle: TextStyles.subheading, - color: _hasError - ? context.theme.colors.coral - : _hasFocus() - ? widget.focusedLabelColor ?? context.theme.colors.blue - : widget.labelColor ?? context.theme.colors.disabled, - ), - counterText: widget.showCounterText ? null : '', - errorMaxLines: widget.errorMaxLines, - prefix: widget.prefix, - prefixStyle: context.theme.textStyle( - textStyle: TextStyles.subheading, - color: _getTextFieldColor(), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: context.theme.colors.coral), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: context.theme.colors.coral), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + labelStyle: context.theme.textStyle( + textStyle: TextStyles.subheading, + color: _hasError + ? context.theme.colors.coral + : _hasFocus() + ? widget.focusedLabelColor ?? context.theme.colors.blue + : widget.labelColor ?? context.theme.colors.disabled, + ), + counterText: widget.showCounterText ? null : '', + errorMaxLines: widget.errorMaxLines, + prefix: widget.prefix, + prefixStyle: context.theme.textStyle( + textStyle: TextStyles.subheading, + color: _getTextFieldColor(), + ), + suffixIcon: widget.suffixIcon, ), - suffixIcon: widget.suffixIcon, + onEditingComplete: widget.onEditingComplete, + validator: _validator, + onChanged: _onChanged, + onTap: widget.onTap, ), - onEditingComplete: widget.onEditingComplete, - validator: _validator, - onChanged: _onChanged, - onTap: widget.onTap, ); bool _hasFocus() => widget.focusNode?.hasFocus ?? false;