From 48f652e6eada339570a2a4a0668ccbeee49ed00d Mon Sep 17 00:00:00 2001 From: Stein Milder Date: Tue, 27 Sep 2022 10:43:22 +0200 Subject: [PATCH] refactor following feedback --- lib/backend/login_repository.dart | 1 - lib/login_config.dart | 20 - lib/plugins/login/forgot_password.dart | 148 +++---- lib/plugins/login/login_email_password.dart | 404 +++++++++--------- lib/plugins/login/login_phone_number.dart | 3 - .../login/login_phone_number_verify.dart | 1 - lib/plugins/login/login_social_buttons.dart | 3 +- 7 files changed, 282 insertions(+), 298 deletions(-) diff --git a/lib/backend/login_repository.dart b/lib/backend/login_repository.dart index 8e7d5f5..6a3a854 100644 --- a/lib/backend/login_repository.dart +++ b/lib/backend/login_repository.dart @@ -29,7 +29,6 @@ abstract class LoginRespositoryWithPhoneNumber extends LoginRepository { )? onCodeSent, void Function(String errorCode)? onVerificationFailed, - void Function(LoginUser? user)? onAutoLogin, }); Future signInWithSMSCode( String verificationId, diff --git a/lib/login_config.dart b/lib/login_config.dart index f0b4554..4e8dcab 100644 --- a/lib/login_config.dart +++ b/lib/login_config.dart @@ -30,13 +30,6 @@ enum LoginMethod { LoginInteractiveWithPhoneNumber, } -enum AutoLoginMode { - alwaysOn, - alwaysOff, - defaultOn, - defaultOff, -} - enum SocialLoginMethod { FaceBook, Google, @@ -151,7 +144,6 @@ class ConfigData { class LoginOptions { const LoginOptions({ - this.autoLoginMode = AutoLoginMode.defaultOn, this.loginImage = '', this.loginEmail, this.loginPassword, @@ -212,18 +204,6 @@ class LoginOptions { /// If you want to change the registration background image, view [RegistrationOptions.backgroundImage] final String? backgroundImage; - /// [AutoLoginMode.defaultOn] - /// Auto login can be enabled/disabled using a checkbox on the login screen. - /// auto login is enabled by default. - /// [AutoLoginMode.defaultOff] - /// Auto login can be enabled/disabled using a checkbox on the login screen. - /// auto login is disabled by default. - /// [AutoLoginMode.alwaysOn] - /// Auto login is always on, there is no way to disable it. - /// [AutoLoginMode.alwaysOff] - /// Auto login is always off, there is no way to enable it. - final AutoLoginMode autoLoginMode; - final VoidCallback? onPressForgotPassword; final VoidCallback? onPressRegister; diff --git a/lib/plugins/login/forgot_password.dart b/lib/plugins/login/forgot_password.dart index afad8c6..5806331 100644 --- a/lib/plugins/login/forgot_password.dart +++ b/lib/plugins/login/forgot_password.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_login/flutter_login_view.dart'; -import '../form/inputs/validators/email_validator.dart'; class ForgotPassword extends StatefulWidget { const ForgotPassword({super.key}); @@ -10,8 +9,9 @@ class ForgotPassword extends StatefulWidget { } class ForgotPasswordState extends State { - String? email; - bool showError = false; + String _email = ''; + final _formKey = GlobalKey(); + String _emailErrorMessage = ''; @override Widget build(BuildContext context) { @@ -40,6 +40,40 @@ class ForgotPasswordState extends State { ), ); + void onPressBtn() { + if (!_formKey.currentState!.validate()) { + return; + } + + context.loginRepository().forgotPassword(_email).then( + (response) { + if (response) { + showAlert( + title: context.translate( + 'forgot_password.dialog.text.title', + ), + text: context.translate( + 'forgot_password.dialog.text.body', + arguments: [_email], + ), + buttonTitle: context.translate( + 'forgot_password.dialog.text.button', + ), + buttonAction: () { + Navigator.pop(context); + }, + ); + } else { + _emailErrorMessage = context.translate( + 'forgot_password.error.email_does_not_exist', + ); + _formKey.currentState?.validate(); + _emailErrorMessage = ''; + } + }, + ); + } + return Material( child: Scaffold( backgroundColor: Theme.of(context).backgroundColor, @@ -61,48 +95,50 @@ class ForgotPasswordState extends State { textAlign: TextAlign.left, ), ), - Padding( - padding: const EdgeInsets.only(left: 30, right: 30), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(bottom: 20), - ), - context.login().config.appTheme.inputs.textField( - title: context.translate( + Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.only( + left: 30, + right: 30, + ), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 20), + ), + TextFormField( + decoration: InputDecoration( + labelText: context.translate( 'forgot_password.input.email', + defaultValue: 'Email address', ), - validators: [ - EmailValidator( - errorMessage: context.translate( - 'forgot_password.error.invalid_email', - ), - ) - ], - onChange: (value, valid) { - setState(() { - showError = false; - }); - if (valid) { - email = value; - } + ), + onChanged: (String value) => setState( + () { + _email = value; }, ), - if (showError) ...[ - Text( - context.translate( - 'forgot_password.error.email_does_not_exist', - ), - style: - Theme.of(context).textTheme.bodyText2!.copyWith( - color: Theme.of(context).errorColor, - ), + validator: (value) { + if (_emailErrorMessage.isNotEmpty) { + return _emailErrorMessage; + } + + if (!RegExp( + r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+", + ).hasMatch(value!)) { + return context.translate( + 'forgot_password.error.invalid_email', + ); + } + return null; + }, + ), + const Padding( + padding: EdgeInsets.only(bottom: 30), ), ], - const Padding( - padding: EdgeInsets.only(bottom: 30), - ), - ], + ), ), ), ], @@ -117,39 +153,7 @@ class ForgotPasswordState extends State { context.translate('forgot_password.button.submit'), style: Theme.of(context).textTheme.button, ), - onPressed: () async { - if (email != null) { - setState(() { - showError = false; - }); - - var result = await context - .loginRepository() - .forgotPassword(email!); - - if (result) { - showAlert( - title: context.translate( - 'forgot_password.dialog.text.title', - ), - text: context.translate( - 'forgot_password.dialog.text.body', - arguments: [email], - ), - buttonTitle: context.translate( - 'forgot_password.dialog.text.button', - ), - buttonAction: () { - Navigator.pop(context); - }, - ); - } else { - setState(() { - showError = true; - }); - } - } - }, + onPressed: onPressBtn, ), ), ], diff --git a/lib/plugins/login/login_email_password.dart b/lib/plugins/login/login_email_password.dart index 4535327..e7c4003 100644 --- a/lib/plugins/login/login_email_password.dart +++ b/lib/plugins/login/login_email_password.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_login/flutter_login_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../form/fields/obscure_text_input_field.dart'; import 'forgot_password.dart'; import 'login_await_email.dart'; import 'login_image.dart'; @@ -30,12 +29,13 @@ class EmailPasswordLogin extends Login { class EmailLoginState extends LoginState { String email = ''; - String error = ''; String password = ''; - bool? autoLogin; late SharedPreferences prefs; bool passwordLess = false; bool _loading = false; + final _formKey = GlobalKey(); + String _emailErrorMessage = ''; + String _passwordErrorMessage = ''; @override void initState() { @@ -65,55 +65,84 @@ class EmailLoginState extends LoginState { } Future _handleLoginPress() async { + if (!_formKey.currentState!.validate()) { + return; + } + + var loginRepository = context.loginRepository(); + setState(() { _loading = true; }); - await prefs.setBool( - 'autoLogin', - context.login().config.loginOptions.autoLoginMode == - AutoLoginMode.defaultOn, - ); - if (!passwordLess && (EmailPasswordLogin.finalEmail != null) && (EmailPasswordLogin.finalPassword != null)) { - if (!(await context.loginRepository().login( + await loginRepository + .login( EmailPasswordLogin.finalEmail!, EmailPasswordLogin.finalPassword!, - ))) { - setState(() { - error = context.loginRepository().getLoginError(); - _loading = false; - }); - } else { - navigateFadeToReplace( - context, - (context) => widget.child, - popRemaining: true, - ); - } + ) + .then( + (response) => response + ? navigateFadeToReplace( + context, + (context) => widget.child, + popRemaining: true, + ) + : setState( + () { + parseLoginMessage(); + _loading = false; + }, + ), + ); } else if (passwordLess && (EmailPasswordLogin.finalEmail != null)) { - if (await context - .loginRepository() - .sendLoginEmail(EmailPasswordLogin.finalEmail!)) { - navigateFadeToReplace( - context, - (ctx) => LoginAwaitEmailScreen( - child: widget.child, - loginComplete: () async { - navigateFadeToReplace(ctx, (ctx) => widget.child!); - }, - ), - popRemaining: true, - ); - } else { - setState(() { - error = 'login.error.user_or_password_unknown'; - _loading = false; - }); - } + await loginRepository + .sendLoginEmail( + EmailPasswordLogin.finalEmail!, + ) + .then( + (response) => response + ? navigateFadeToReplace( + context, + (ctx) => LoginAwaitEmailScreen( + child: widget.child, + loginComplete: () async { + navigateFadeToReplace(ctx, (ctx) => widget.child!); + }, + ), + popRemaining: true, + ) + : setState( + () { + _emailErrorMessage = + 'login.error.user_or_password_unknown'; + _loading = false; + }, + ), + ); } + + _formKey.currentState!.validate(); + _emailErrorMessage = ''; + _passwordErrorMessage = ''; + } + + void parseLoginMessage() { + var loginRepository = context.loginRepository(); + var loginError = loginRepository.getLoginError(); + + setState(() { + print(loginError); + switch (loginError) { + case 'login.error.invalid_email': + _emailErrorMessage = loginError; + break; + default: + _passwordErrorMessage = loginError; + } + }); } @override @@ -167,186 +196,163 @@ class EmailLoginState extends LoginState { : 0, ), child: Container( - child: Column( - children: [ - context.login().config.appTheme.inputs.textField( - value: EmailPasswordLogin.finalEmail ?? '', - onChange: (val, valid) { - if (valid) { - onEmailChanged(val); - } - }, - keyboardType: TextInputType.emailAddress, - title: context.translate( + child: Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + initialValue: EmailPasswordLogin.finalEmail ?? '', + onChanged: onEmailChanged, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: context.translate( 'login.input.email', defaultValue: 'Email address', ), ), - if (!passwordLess) ...[ - Padding( - padding: const EdgeInsets.only(top: 20), - child: ObscureTextInputField( - value: EmailPasswordLogin.finalPassword, - title: context.translate( - 'login.input.password', - defaultValue: 'Password', - ), - onChange: (val, valid) { - if (valid) { - onPasswordChanged(val); - } - }, - ), + validator: (value) { + if (_emailErrorMessage != '') { + return context.translate(_emailErrorMessage); + } + if (value == null || value.isEmpty) { + return 'Please enter an email address'; + } + return null; + }, ), - ], - const Padding( - padding: EdgeInsets.only(top: 20), - ), - Container( - alignment: Alignment.centerRight, - child: context - .login() - .config - .appTheme - .buttons - .tertiaryButton( - context: context, - child: Text( - context.translate('login.button.forgot_password'), - style: Theme.of(context).textTheme.bodyText2, + if (!passwordLess) ...[ + Padding( + padding: const EdgeInsets.only(top: 20), + child: TextFormField( + initialValue: EmailPasswordLogin.finalPassword ?? '', + obscureText: true, + onChanged: onPasswordChanged, + decoration: InputDecoration( + labelText: context.translate( + 'login.input.password', + defaultValue: 'Password', + ), ), - onPressed: widget.onPressedForgotPassword ?? - () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const ForgotPassword(), - ), - ), + validator: (value) { + if (_passwordErrorMessage != '') { + return context.translate(_passwordErrorMessage); + } + if (value == null || value.isEmpty) { + return 'Please enter a password'; + } + return null; + }, ), - ), - if (context.login().config.loginOptions.autoLoginMode != - AutoLoginMode.alwaysOff && - context.login().config.loginOptions.autoLoginMode != - AutoLoginMode.alwaysOn) ...[ - Theme( - data: Theme.of(context).copyWith( - textTheme: Theme.of(context).textTheme.copyWith( - headline6: Theme.of(context) - .textTheme - .bodyText2 - ?.copyWith( - color: Theme.of(context) - .textTheme - .headline6 - ?.color, - ), - ), ), - child: context.login().config.appTheme.inputs.checkBox( - value: autoLogin ?? - context - .login() - .config - .loginOptions - .autoLoginMode == - AutoLoginMode.defaultOn, - title: context.translate( - 'login.input.stay_logged_in', - defaultValue: 'Stay logged in', - ), - onChange: (value, valid) => autoLogin = value, - ), + ], + const Padding( + padding: EdgeInsets.only(top: 20), ), - ], - if (error.isNotEmpty) ...[ Container( - padding: const EdgeInsets.only(top: 20, bottom: 20), - child: Text( - error == '' - ? error - : context.translate( - error, - defaultValue: - 'An error occurred when logging in: $error', - ), - style: Theme.of(context) - .textTheme - .bodyText1! - .copyWith(color: Theme.of(context).errorColor), - ), - ), - ], - FlutterLogin.of(context) - .config - .appTheme - .buttons - .primaryButton( - context: context, - isLoading: _loading, - isDisabled: _loading, - child: Text( - context.translate( - 'login.button.login', - defaultValue: 'Log in', + alignment: Alignment.centerRight, + child: context + .login() + .config + .appTheme + .buttons + .tertiaryButton( + context: context, + child: Text( + context.translate('login.button.forgot_password'), + style: Theme.of(context).textTheme.bodyText2, + ), + onPressed: widget.onPressedForgotPassword ?? + () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const ForgotPassword(), + ), + ), ), - style: Theme.of(context).textTheme.button, - textAlign: TextAlign.center, - ), - onPressed: _handleLoginPress, - ), - if (context.login().config.loginOptions.loginMethod.contains( - LoginMethod.LoginInteractiveWithMagicLink, - ) && - context.login().config.loginOptions.loginMethod.contains( - LoginMethod.LoginInteractiveWithUsernameAndPassword, - )) ...[ + ), FlutterLogin.of(context) .config .appTheme .buttons - .tertiaryButton( - context: context, - child: Text( - passwordLess - ? context.translate( - 'login.button.login_email_password', - defaultValue: 'Log in with password', - ) - : context.translate( - 'login.button.login_email_only', - defaultValue: 'Log in with link', - ), - style: Theme.of(context).textTheme.bodyText1, - ), - onPressed: () => - setState(() => passwordLess = !passwordLess), - ) - ], - if (context - .login() - .config - .registrationOptions - .registrationMode == - RegistrationMode.Enabled) ...[ - context.login().config.appTheme.buttons.tertiaryButton( + .primaryButton( context: context, + isLoading: _loading, + isDisabled: _loading, child: Text( context.translate( - 'login.button.no_account', - defaultValue: "I don't have an account yet", + 'login.button.login', + defaultValue: 'Log in', ), - style: Theme.of(context).textTheme.bodyText1, + style: Theme.of(context).textTheme.button, + textAlign: TextAlign.center, ), - onPressed: () { - FlutterLogin.of(context) - .config - .loginOptions - .onPressRegister!(); - }, + onPressed: _handleLoginPress, ), + if (context + .login() + .config + .loginOptions + .loginMethod + .contains( + LoginMethod.LoginInteractiveWithMagicLink, + ) && + context + .login() + .config + .loginOptions + .loginMethod + .contains( + LoginMethod + .LoginInteractiveWithUsernameAndPassword, + )) ...[ + FlutterLogin.of(context) + .config + .appTheme + .buttons + .tertiaryButton( + context: context, + child: Text( + passwordLess + ? context.translate( + 'login.button.login_email_password', + defaultValue: 'Log in with password', + ) + : context.translate( + 'login.button.login_email_only', + defaultValue: 'Log in with link', + ), + style: Theme.of(context).textTheme.bodyText1, + ), + onPressed: () => + setState(() => passwordLess = !passwordLess), + ) + ], + if (context + .login() + .config + .registrationOptions + .registrationMode == + RegistrationMode.Enabled) ...[ + context.login().config.appTheme.buttons.tertiaryButton( + context: context, + child: Text( + context.translate( + 'login.button.no_account', + defaultValue: "I don't have an account yet", + ), + style: Theme.of(context).textTheme.bodyText1, + ), + onPressed: () { + FlutterLogin.of(context) + .config + .loginOptions + .onPressRegister!(); + }, + ), + ], ], - ], + ), ), ), ), diff --git a/lib/plugins/login/login_phone_number.dart b/lib/plugins/login/login_phone_number.dart index 3fd2e09..a57d5ff 100644 --- a/lib/plugins/login/login_phone_number.dart +++ b/lib/plugins/login/login_phone_number.dart @@ -137,7 +137,6 @@ class LoginPhoneNumberState extends State onLogin: _onLoggedIn, ), ), - onAutoLogin: (_) => _onLoggedIn, onVerificationFailed: (String errorCode) { if (errorCode == 'invalid-phone-number') { setState(() { @@ -170,8 +169,6 @@ class LoginPhoneNumberState extends State } Future _onLoggedIn(LoginUser? user) async { - var prefs = await SharedPreferences.getInstance(); - await prefs.setBool('autoLogin', true); if (user != null) { if (await context.loginRepository().isRegistrationRequired(user)) { widget.navRegistration.call(); diff --git a/lib/plugins/login/login_phone_number_verify.dart b/lib/plugins/login/login_phone_number_verify.dart index 3522a8e..38d8b14 100644 --- a/lib/plugins/login/login_phone_number_verify.dart +++ b/lib/plugins/login/login_phone_number_verify.dart @@ -162,7 +162,6 @@ class LoginPhoneNumberVerifyState extends State ), ); }, - onAutoLogin: (user) => widget.onLogin.call(user), ); }, ), diff --git a/lib/plugins/login/login_social_buttons.dart b/lib/plugins/login/login_social_buttons.dart index 2168e22..7f9cf19 100644 --- a/lib/plugins/login/login_social_buttons.dart +++ b/lib/plugins/login/login_social_buttons.dart @@ -83,8 +83,7 @@ class LoginSocialButtons extends StatelessWidget { interactionType: SocialInteractionType.Login, ), ); - var prefs = await SharedPreferences.getInstance(); - await prefs.setBool('autoLogin', true); + if (user != null) { if (await repository.isRegistrationRequired(user)) { navigateRegistration();