From b4c34c8a81bf1b1e52f06967efce8ed4581d8243 Mon Sep 17 00:00:00 2001 From: charlcl180 <93603028+charlcl180@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:48:23 -0400 Subject: [PATCH] implementation of seperation widget of login_view --- lib/features/welcome/login/login_view.dart | 298 +++--------------- .../welcome/login/widget/app_ets_logo.dart | 29 ++ .../login/widget/forgot_password_link.dart | 42 +++ .../welcome/login/widget/login_button.dart | 55 ++++ .../welcome/login/widget/login_widget.dart | 25 ++ .../welcome/login/widget/need_help_link.dart | 36 +++ .../login/widget/universal_code_field.dart | 65 ++++ 7 files changed, 303 insertions(+), 247 deletions(-) create mode 100644 lib/features/welcome/login/widget/app_ets_logo.dart create mode 100644 lib/features/welcome/login/widget/forgot_password_link.dart create mode 100644 lib/features/welcome/login/widget/login_button.dart create mode 100644 lib/features/welcome/login/widget/login_widget.dart create mode 100644 lib/features/welcome/login/widget/need_help_link.dart create mode 100644 lib/features/welcome/login/widget/universal_code_field.dart diff --git a/lib/features/welcome/login/login_view.dart b/lib/features/welcome/login/login_view.dart index a71d7618b..1f5e309db 100644 --- a/lib/features/welcome/login/login_view.dart +++ b/lib/features/welcome/login/login_view.dart @@ -1,24 +1,21 @@ -// Flutter imports: -import 'package:flutter/material.dart'; +// LoginView.dart -// Package imports: +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:notredame/features/welcome/login/widget/app_ets_logo.dart'; +import 'package:notredame/features/welcome/login/widget/forgot_password_link.dart'; +import 'package:notredame/features/welcome/login/widget/login_button.dart'; +import 'package:notredame/features/welcome/login/widget/login_widget.dart'; +import 'package:notredame/features/welcome/login/widget/need_help_link.dart'; +import 'package:notredame/features/welcome/login/widget/universal_code_field.dart'; import 'package:stacked/stacked.dart'; -// Project imports: -import 'package:notredame/features/app/navigation/router_paths.dart'; -import 'package:notredame/features/app/integration/launch_url_service.dart'; -import 'package:notredame/features/app/navigation/navigation_service.dart'; -import 'package:notredame/features/app/analytics/remote_config_service.dart'; -import 'package:notredame/features/welcome/login/login_mask.dart'; -import 'package:notredame/utils/utils.dart'; import 'package:notredame/features/welcome/login/login_viewmodel.dart'; -import 'package:notredame/utils/locator.dart'; import 'package:notredame/utils/app_theme.dart'; +import 'package:notredame/utils/utils.dart'; import 'package:notredame/features/welcome/widgets/password_text_field.dart'; + class LoginView extends StatefulWidget { @override _LoginViewState createState() => _LoginViewState(); @@ -29,13 +26,6 @@ class _LoginViewState extends State { final FocusScopeNode _focusNode = FocusScopeNode(); - final NavigationService _navigationService = locator(); - - final LaunchUrlService _launchUrlService = locator(); - - final RemoteConfigService _remoteConfigService = - locator(); - /// Unique key of the login form form final GlobalKey formKey = GlobalKey(); @@ -43,235 +33,58 @@ class _LoginViewState extends State { final GlobalKey tooltipkey = GlobalKey(); @override - Widget build(BuildContext context) => - ViewModelBuilder.reactive( + Widget build(BuildContext context) => ViewModelBuilder.reactive( viewModelBuilder: () => LoginViewModel(intl: AppIntl.of(context)!), builder: (context, model, child) => Scaffold( backgroundColor: Utils.getColorByBrightness( context, AppTheme.etsLightRed, AppTheme.primaryDark), resizeToAvoidBottomInset: false, body: Builder( - builder: (BuildContext context) => SafeArea( - minimum: const EdgeInsets.all(20.0), - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Form( - key: formKey, - onChanged: () { - setState(() { - formKey.currentState?.validate(); - }); - }, - child: AutofillGroup( - child: FocusScope( - node: _focusNode, - child: Column( - children: [ - const SizedBox( - height: 48, - ), - Hero( - tag: 'ets_logo', - child: SvgPicture.asset( - "assets/images/ets_white_logo.svg", - excludeFromSemantics: true, - width: 90, - height: 90, - colorFilter: ColorFilter.mode( - Theme.of(context).brightness == - Brightness.light - ? Colors.white - : AppTheme.etsLightRed, - BlendMode.srcIn), - )), - const SizedBox( - height: 48, - ), - TextFormField( - autofillHints: const [ - AutofillHints.username - ], - cursorColor: Colors.white, - keyboardType: - TextInputType.visiblePassword, - decoration: InputDecoration( - enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white70)), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white, - width: borderRadiusOnFocus)), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: errorTextColor, - width: borderRadiusOnFocus)), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: errorTextColor, - width: borderRadiusOnFocus)), - labelText: AppIntl.of(context)! - .login_prompt_universal_code, - labelStyle: const TextStyle( - color: Colors.white54), - errorStyle: - TextStyle(color: errorTextColor), - suffixIcon: Tooltip( - key: tooltipkey, - triggerMode: - TooltipTriggerMode.manual, - message: AppIntl.of(context)! - .universal_code_example, - preferBelow: true, - child: IconButton( - icon: const Icon(Icons.help, - color: Colors.white), - onPressed: () { - tooltipkey.currentState - ?.ensureTooltipVisible(); - }, - )), - ), - autofocus: true, - style: - const TextStyle(color: Colors.white), - onEditingComplete: _focusNode.nextFocus, - validator: model.validateUniversalCode, - initialValue: model.universalCode, - inputFormatters: [ - LoginMask(), - ], - ), - const SizedBox( - height: 16, - ), - PasswordFormField( - validator: model.validatePassword, - onEditionComplete: - _focusNode.nextFocus), - Align( - alignment: Alignment.topRight, - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: InkWell( - child: Text( - AppIntl.of(context)! - .forgot_password, - style: const TextStyle( - decoration: - TextDecoration.underline, - color: Colors.white), - ), - onTap: () { - final signetsPasswordResetUrl = - _remoteConfigService - .signetsPasswordResetUrl; - if (signetsPasswordResetUrl != "") { - _launchUrlService.launchInBrowser( - _remoteConfigService - .signetsPasswordResetUrl, - Theme.of(context).brightness); - } else { - Fluttertoast.showToast( - msg: AppIntl.of(context)! - .error); - } - }, - ), - ), - ), - const SizedBox( - height: 24, - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: !model.canSubmit - ? null - : () async { - final String error = - await model.authenticate(); - - setState(() { - if (error.isNotEmpty) { - Fluttertoast.showToast( - msg: error); - } - formKey.currentState?.reset(); - }); - }, - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all( - model.canSubmit - ? colorButton - : Colors.white38), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric( - vertical: 16)), - ), - child: Text( - AppIntl.of(context)! - .login_action_sign_in, - style: TextStyle( - color: model.canSubmit - ? submitTextColor - : Colors.white60, - fontSize: 18), - ), - ), - ), - Center( - child: Padding( - padding: const EdgeInsets.only(top: 24), - child: InkWell( - child: Text( - AppIntl.of(context)!.need_help, - style: const TextStyle( - decoration: - TextDecoration.underline, - color: Colors.white), - ), - onTap: () async { - _navigationService.pushNamed( - RouterPaths.faq, - arguments: - Utils.getColorByBrightness( - context, - AppTheme.etsLightRed, - AppTheme.primaryDark)); - }, - ), - ), - ), - ], - ), - ), - ), - ), - Wrap( - direction: Axis.vertical, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: -20, - children: [ - Text( - AppIntl.of(context)!.login_applets_logo, - style: const TextStyle(color: Colors.white), - ), - Image.asset( - 'assets/images/applets_white_logo.png', - excludeFromSemantics: true, - width: 100, - height: 100, + builder: (BuildContext context) => SafeArea( + minimum: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Form( + key: formKey, + onChanged: () { + setState(() { + formKey.currentState?.validate(); + }); + }, + child: AutofillGroup( + child: FocusScope( + node: _focusNode, + child: Column( + children: [ + const SizedBox(height: 48), + const LogoWidget(), + const SizedBox(height: 48), + UniversalCodeField( + borderRadiusOnFocus: borderRadiusOnFocus, + tooltipKey: tooltipkey, + model: model, ), + const SizedBox(height: 16), + PasswordFormField( + validator: model.validatePassword, + onEditionComplete: _focusNode.nextFocus), + ForgotPasswordLink(), + const SizedBox(height: 24), + LoginButton(formKey: formKey, model: model), + NeedHelpLink(), ], ), - ], + ), ), ), - )), + const AppEtsLogo(), + ], + ), + ), + ), + ), ), ); @@ -280,13 +93,4 @@ class _LoginViewState extends State { _focusNode.dispose(); super.dispose(); } - - Color get errorTextColor => - Utils.getColorByBrightness(context, Colors.amberAccent, Colors.redAccent); - - Color get colorButton => - Utils.getColorByBrightness(context, Colors.white, AppTheme.etsLightRed); - - Color get submitTextColor => - Utils.getColorByBrightness(context, AppTheme.etsLightRed, Colors.white); } diff --git a/lib/features/welcome/login/widget/app_ets_logo.dart b/lib/features/welcome/login/widget/app_ets_logo.dart new file mode 100644 index 000000000..6184c2463 --- /dev/null +++ b/lib/features/welcome/login/widget/app_ets_logo.dart @@ -0,0 +1,29 @@ +// widgets/AppletsLogo.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class AppEtsLogo extends StatelessWidget { + const AppEtsLogo({super.key}); + + @override + Widget build(BuildContext context) { + return Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: -20, + children: [ + Text( + AppIntl.of(context)!.login_applets_logo, + style: const TextStyle(color: Colors.white), + ), + Image.asset( + 'assets/images/applets_white_logo.png', + excludeFromSemantics: true, + width: 100, + height: 100, + ), + ], + ); + } +} diff --git a/lib/features/welcome/login/widget/forgot_password_link.dart b/lib/features/welcome/login/widget/forgot_password_link.dart new file mode 100644 index 000000000..3eee453bc --- /dev/null +++ b/lib/features/welcome/login/widget/forgot_password_link.dart @@ -0,0 +1,42 @@ +// widgets/ForgotPasswordLink.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; +import 'package:notredame/features/app/analytics/remote_config_service.dart'; +import 'package:notredame/utils/locator.dart'; + +class ForgotPasswordLink extends StatelessWidget { + final LaunchUrlService _launchUrlService = locator(); + final RemoteConfigService _remoteConfigService = locator(); + + ForgotPasswordLink({super.key}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.only(top: 4), + child: InkWell( + child: Text( + AppIntl.of(context)!.forgot_password, + style: const TextStyle( + decoration: TextDecoration.underline, color: Colors.white), + ), + onTap: () { + final signetsPasswordResetUrl = _remoteConfigService.signetsPasswordResetUrl; + if (signetsPasswordResetUrl != "") { + _launchUrlService.launchInBrowser( + _remoteConfigService.signetsPasswordResetUrl, + Theme.of(context).brightness); + } else { + Fluttertoast.showToast(msg: AppIntl.of(context)!.error); + } + }, + ), + ), + ); + } +} diff --git a/lib/features/welcome/login/widget/login_button.dart b/lib/features/welcome/login/widget/login_button.dart new file mode 100644 index 000000000..f2284ea9b --- /dev/null +++ b/lib/features/welcome/login/widget/login_button.dart @@ -0,0 +1,55 @@ +// widgets/LoginButton.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:notredame/features/welcome/login/login_viewmodel.dart'; +import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/app_theme.dart'; + + +class LoginButton extends StatelessWidget { + final GlobalKey formKey; + final LoginViewModel model; + + const LoginButton({super.key, required this.formKey, required this.model}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: !model.canSubmit + ? null + : () async { + final String error = await model.authenticate(); + + if (error.isNotEmpty) { + Fluttertoast.showToast(msg: error); + } + formKey.currentState?.reset(); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + model.canSubmit ? colorButton(context) : Colors.white38), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 16)), + ), + child: Text( + AppIntl.of(context)!.login_action_sign_in, + style: TextStyle( + color: model.canSubmit + ? submitTextColor(context) + : Colors.white60, + fontSize: 18), + ), + ), + ); + } + + Color colorButton(BuildContext context) => Utils.getColorByBrightness( + context, Colors.white, AppTheme.etsLightRed); + + Color submitTextColor(BuildContext context) => Utils.getColorByBrightness( + context, AppTheme.etsLightRed, Colors.white); +} diff --git a/lib/features/welcome/login/widget/login_widget.dart b/lib/features/welcome/login/widget/login_widget.dart new file mode 100644 index 000000000..83670f454 --- /dev/null +++ b/lib/features/welcome/login/widget/login_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:notredame/utils/app_theme.dart'; + +class LogoWidget extends StatelessWidget { + const LogoWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Hero( + tag: 'ets_logo', + child: SvgPicture.asset( + "assets/images/ets_white_logo.svg", + excludeFromSemantics: true, + width: 90, + height: 90, + colorFilter: ColorFilter.mode( + Theme.of(context).brightness == Brightness.light + ? Colors.white + : AppTheme.etsLightRed, + BlendMode.srcIn), + ), + ); + } +} diff --git a/lib/features/welcome/login/widget/need_help_link.dart b/lib/features/welcome/login/widget/need_help_link.dart new file mode 100644 index 000000000..38d5db0ca --- /dev/null +++ b/lib/features/welcome/login/widget/need_help_link.dart @@ -0,0 +1,36 @@ +// widgets/NeedHelpLink.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:notredame/features/app/navigation/navigation_service.dart'; +import 'package:notredame/features/app/navigation/router_paths.dart'; +import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/utils/app_theme.dart'; + +class NeedHelpLink extends StatelessWidget { + final NavigationService _navigationService = locator(); + + NeedHelpLink({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.only(top: 24), + child: InkWell( + child: Text( + AppIntl.of(context)!.need_help, + style: const TextStyle( + decoration: TextDecoration.underline, color: Colors.white), + ), + onTap: () async { + _navigationService.pushNamed(RouterPaths.faq, + arguments: Utils.getColorByBrightness( + context, AppTheme.etsLightRed, AppTheme.primaryDark)); + }, + ), + ), + ); + } +} diff --git a/lib/features/welcome/login/widget/universal_code_field.dart b/lib/features/welcome/login/widget/universal_code_field.dart new file mode 100644 index 000000000..8b4457316 --- /dev/null +++ b/lib/features/welcome/login/widget/universal_code_field.dart @@ -0,0 +1,65 @@ +// widgets/UniversalCodeField.dart + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:notredame/features/welcome/login/login_mask.dart'; +import 'package:notredame/features/welcome/login/login_viewmodel.dart'; +import 'package:notredame/utils/utils.dart'; + +class UniversalCodeField extends StatelessWidget { + final double borderRadiusOnFocus; + final GlobalKey tooltipKey; + final LoginViewModel model; + + const UniversalCodeField({ + super.key, + required this.borderRadiusOnFocus, + required this.tooltipKey, + required this.model, + }); + + @override + Widget build(BuildContext context) { + return TextFormField( + autofillHints: const [AutofillHints.username], + cursorColor: Colors.white, + keyboardType: TextInputType.visiblePassword, + decoration: InputDecoration( + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.white70)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, width: borderRadiusOnFocus)), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: errorTextColor(context), width: borderRadiusOnFocus)), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: errorTextColor(context), width: borderRadiusOnFocus)), + labelText: AppIntl.of(context)!.login_prompt_universal_code, + labelStyle: const TextStyle(color: Colors.white54), + errorStyle: TextStyle(color: errorTextColor(context)), + suffixIcon: Tooltip( + key: tooltipKey, + triggerMode: TooltipTriggerMode.manual, + message: AppIntl.of(context)!.universal_code_example, + preferBelow: true, + child: IconButton( + icon: const Icon(Icons.help, color: Colors.white), + onPressed: () { + tooltipKey.currentState?.ensureTooltipVisible(); + }, + )), + ), + autofocus: true, + style: const TextStyle(color: Colors.white), + onEditingComplete: () => FocusScope.of(context).nextFocus(), + validator: model.validateUniversalCode, + initialValue: model.universalCode, + inputFormatters: [LoginMask()], + ); + } + + Color errorTextColor(BuildContext context) => + Utils.getColorByBrightness(context, Colors.amberAccent, Colors.redAccent); +}