refactor following feedback

This commit is contained in:
Stein Milder 2022-09-27 10:43:22 +02:00
parent b40fc7fb95
commit 48f652e6ea
7 changed files with 282 additions and 298 deletions

View file

@ -29,7 +29,6 @@ abstract class LoginRespositoryWithPhoneNumber extends LoginRepository {
)? )?
onCodeSent, onCodeSent,
void Function(String errorCode)? onVerificationFailed, void Function(String errorCode)? onVerificationFailed,
void Function(LoginUser? user)? onAutoLogin,
}); });
Future<LoginUser?> signInWithSMSCode( Future<LoginUser?> signInWithSMSCode(
String verificationId, String verificationId,

View file

@ -30,13 +30,6 @@ enum LoginMethod {
LoginInteractiveWithPhoneNumber, LoginInteractiveWithPhoneNumber,
} }
enum AutoLoginMode {
alwaysOn,
alwaysOff,
defaultOn,
defaultOff,
}
enum SocialLoginMethod { enum SocialLoginMethod {
FaceBook, FaceBook,
Google, Google,
@ -151,7 +144,6 @@ class ConfigData {
class LoginOptions { class LoginOptions {
const LoginOptions({ const LoginOptions({
this.autoLoginMode = AutoLoginMode.defaultOn,
this.loginImage = '', this.loginImage = '',
this.loginEmail, this.loginEmail,
this.loginPassword, this.loginPassword,
@ -212,18 +204,6 @@ class LoginOptions {
/// If you want to change the registration background image, view [RegistrationOptions.backgroundImage] /// If you want to change the registration background image, view [RegistrationOptions.backgroundImage]
final String? 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? onPressForgotPassword;
final VoidCallback? onPressRegister; final VoidCallback? onPressRegister;

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login_view.dart'; import 'package:flutter_login/flutter_login_view.dart';
import '../form/inputs/validators/email_validator.dart';
class ForgotPassword extends StatefulWidget { class ForgotPassword extends StatefulWidget {
const ForgotPassword({super.key}); const ForgotPassword({super.key});
@ -10,8 +9,9 @@ class ForgotPassword extends StatefulWidget {
} }
class ForgotPasswordState extends State<ForgotPassword> { class ForgotPasswordState extends State<ForgotPassword> {
String? email; String _email = '';
bool showError = false; final _formKey = GlobalKey<FormState>();
String _emailErrorMessage = '';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -40,6 +40,40 @@ class ForgotPasswordState extends State<ForgotPassword> {
), ),
); );
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( return Material(
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
@ -61,48 +95,50 @@ class ForgotPasswordState extends State<ForgotPassword> {
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
), ),
Padding( Form(
padding: const EdgeInsets.only(left: 30, right: 30), key: _formKey,
child: Column( child: Padding(
children: [ padding: const EdgeInsets.only(
const Padding( left: 30,
padding: EdgeInsets.only(bottom: 20), right: 30,
), ),
context.login().config.appTheme.inputs.textField( child: Column(
title: context.translate( children: [
const Padding(
padding: EdgeInsets.only(bottom: 20),
),
TextFormField(
decoration: InputDecoration(
labelText: context.translate(
'forgot_password.input.email', 'forgot_password.input.email',
defaultValue: 'Email address',
), ),
validators: [ ),
EmailValidator( onChanged: (String value) => setState(
errorMessage: context.translate( () {
'forgot_password.error.invalid_email', _email = value;
),
)
],
onChange: (value, valid) {
setState(() {
showError = false;
});
if (valid) {
email = value;
}
}, },
), ),
if (showError) ...[ validator: (value) {
Text( if (_emailErrorMessage.isNotEmpty) {
context.translate( return _emailErrorMessage;
'forgot_password.error.email_does_not_exist', }
),
style: if (!RegExp(
Theme.of(context).textTheme.bodyText2!.copyWith( r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+",
color: Theme.of(context).errorColor, ).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<ForgotPassword> {
context.translate('forgot_password.button.submit'), context.translate('forgot_password.button.submit'),
style: Theme.of(context).textTheme.button, style: Theme.of(context).textTheme.button,
), ),
onPressed: () async { onPressed: onPressBtn,
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;
});
}
}
},
), ),
), ),
], ],

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login_view.dart'; import 'package:flutter_login/flutter_login_view.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../form/fields/obscure_text_input_field.dart';
import 'forgot_password.dart'; import 'forgot_password.dart';
import 'login_await_email.dart'; import 'login_await_email.dart';
import 'login_image.dart'; import 'login_image.dart';
@ -30,12 +29,13 @@ class EmailPasswordLogin extends Login {
class EmailLoginState extends LoginState<EmailPasswordLogin> { class EmailLoginState extends LoginState<EmailPasswordLogin> {
String email = ''; String email = '';
String error = '';
String password = ''; String password = '';
bool? autoLogin;
late SharedPreferences prefs; late SharedPreferences prefs;
bool passwordLess = false; bool passwordLess = false;
bool _loading = false; bool _loading = false;
final _formKey = GlobalKey<FormState>();
String _emailErrorMessage = '';
String _passwordErrorMessage = '';
@override @override
void initState() { void initState() {
@ -65,55 +65,84 @@ class EmailLoginState extends LoginState<EmailPasswordLogin> {
} }
Future<void> _handleLoginPress() async { Future<void> _handleLoginPress() async {
if (!_formKey.currentState!.validate()) {
return;
}
var loginRepository = context.loginRepository();
setState(() { setState(() {
_loading = true; _loading = true;
}); });
await prefs.setBool(
'autoLogin',
context.login().config.loginOptions.autoLoginMode ==
AutoLoginMode.defaultOn,
);
if (!passwordLess && if (!passwordLess &&
(EmailPasswordLogin.finalEmail != null) && (EmailPasswordLogin.finalEmail != null) &&
(EmailPasswordLogin.finalPassword != null)) { (EmailPasswordLogin.finalPassword != null)) {
if (!(await context.loginRepository().login( await loginRepository
.login(
EmailPasswordLogin.finalEmail!, EmailPasswordLogin.finalEmail!,
EmailPasswordLogin.finalPassword!, EmailPasswordLogin.finalPassword!,
))) { )
setState(() { .then(
error = context.loginRepository().getLoginError(); (response) => response
_loading = false; ? navigateFadeToReplace(
}); context,
} else { (context) => widget.child,
navigateFadeToReplace( popRemaining: true,
context, )
(context) => widget.child, : setState(
popRemaining: true, () {
); parseLoginMessage();
} _loading = false;
},
),
);
} else if (passwordLess && (EmailPasswordLogin.finalEmail != null)) { } else if (passwordLess && (EmailPasswordLogin.finalEmail != null)) {
if (await context await loginRepository
.loginRepository() .sendLoginEmail(
.sendLoginEmail(EmailPasswordLogin.finalEmail!)) { EmailPasswordLogin.finalEmail!,
navigateFadeToReplace( )
context, .then(
(ctx) => LoginAwaitEmailScreen( (response) => response
child: widget.child, ? navigateFadeToReplace(
loginComplete: () async { context,
navigateFadeToReplace(ctx, (ctx) => widget.child!); (ctx) => LoginAwaitEmailScreen(
}, child: widget.child,
), loginComplete: () async {
popRemaining: true, navigateFadeToReplace(ctx, (ctx) => widget.child!);
); },
} else { ),
setState(() { popRemaining: true,
error = 'login.error.user_or_password_unknown'; )
_loading = false; : 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 @override
@ -167,186 +196,163 @@ class EmailLoginState extends LoginState<EmailPasswordLogin> {
: 0, : 0,
), ),
child: Container( child: Container(
child: Column( child: Form(
children: <Widget>[ key: _formKey,
context.login().config.appTheme.inputs.textField( child: Column(
value: EmailPasswordLogin.finalEmail ?? '', children: <Widget>[
onChange: (val, valid) { TextFormField(
if (valid) { initialValue: EmailPasswordLogin.finalEmail ?? '',
onEmailChanged(val); onChanged: onEmailChanged,
} keyboardType: TextInputType.emailAddress,
}, decoration: InputDecoration(
keyboardType: TextInputType.emailAddress, labelText: context.translate(
title: context.translate(
'login.input.email', 'login.input.email',
defaultValue: 'Email address', defaultValue: 'Email address',
), ),
), ),
if (!passwordLess) ...[ validator: (value) {
Padding( if (_emailErrorMessage != '') {
padding: const EdgeInsets.only(top: 20), return context.translate(_emailErrorMessage);
child: ObscureTextInputField( }
value: EmailPasswordLogin.finalPassword, if (value == null || value.isEmpty) {
title: context.translate( return 'Please enter an email address';
'login.input.password', }
defaultValue: 'Password', return null;
), },
onChange: (val, valid) {
if (valid) {
onPasswordChanged(val);
}
},
),
), ),
], if (!passwordLess) ...[
const Padding( Padding(
padding: EdgeInsets.only(top: 20), padding: const EdgeInsets.only(top: 20),
), child: TextFormField(
Container( initialValue: EmailPasswordLogin.finalPassword ?? '',
alignment: Alignment.centerRight, obscureText: true,
child: context onChanged: onPasswordChanged,
.login() decoration: InputDecoration(
.config labelText: context.translate(
.appTheme 'login.input.password',
.buttons defaultValue: 'Password',
.tertiaryButton( ),
context: context,
child: Text(
context.translate('login.button.forgot_password'),
style: Theme.of(context).textTheme.bodyText2,
), ),
onPressed: widget.onPressedForgotPassword ?? validator: (value) {
() => Navigator.push( if (_passwordErrorMessage != '') {
context, return context.translate(_passwordErrorMessage);
MaterialPageRoute( }
builder: (context) => if (value == null || value.isEmpty) {
const ForgotPassword(), 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 ?? const Padding(
context padding: EdgeInsets.only(top: 20),
.login()
.config
.loginOptions
.autoLoginMode ==
AutoLoginMode.defaultOn,
title: context.translate(
'login.input.stay_logged_in',
defaultValue: 'Stay logged in',
),
onChange: (value, valid) => autoLogin = value,
),
), ),
],
if (error.isNotEmpty) ...[
Container( Container(
padding: const EdgeInsets.only(top: 20, bottom: 20), alignment: Alignment.centerRight,
child: Text( child: context
error == '' .login()
? error .config
: context.translate( .appTheme
error, .buttons
defaultValue: .tertiaryButton(
'An error occurred when logging in: $error', context: context,
), child: Text(
style: Theme.of(context) context.translate('login.button.forgot_password'),
.textTheme style: Theme.of(context).textTheme.bodyText2,
.bodyText1! ),
.copyWith(color: Theme.of(context).errorColor), onPressed: widget.onPressedForgotPassword ??
), () => Navigator.push(
), context,
], MaterialPageRoute(
FlutterLogin.of(context) builder: (context) =>
.config const ForgotPassword(),
.appTheme ),
.buttons ),
.primaryButton(
context: context,
isLoading: _loading,
isDisabled: _loading,
child: Text(
context.translate(
'login.button.login',
defaultValue: 'Log in',
), ),
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) FlutterLogin.of(context)
.config .config
.appTheme .appTheme
.buttons .buttons
.tertiaryButton( .primaryButton(
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, context: context,
isLoading: _loading,
isDisabled: _loading,
child: Text( child: Text(
context.translate( context.translate(
'login.button.no_account', 'login.button.login',
defaultValue: "I don't have an account yet", defaultValue: 'Log in',
), ),
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.button,
textAlign: TextAlign.center,
), ),
onPressed: () { onPressed: _handleLoginPress,
FlutterLogin.of(context)
.config
.loginOptions
.onPressRegister!();
},
), ),
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!();
},
),
],
], ],
], ),
), ),
), ),
), ),

View file

@ -137,7 +137,6 @@ class LoginPhoneNumberState extends State<LoginPhoneNumber>
onLogin: _onLoggedIn, onLogin: _onLoggedIn,
), ),
), ),
onAutoLogin: (_) => _onLoggedIn,
onVerificationFailed: (String errorCode) { onVerificationFailed: (String errorCode) {
if (errorCode == 'invalid-phone-number') { if (errorCode == 'invalid-phone-number') {
setState(() { setState(() {
@ -170,8 +169,6 @@ class LoginPhoneNumberState extends State<LoginPhoneNumber>
} }
Future<void> _onLoggedIn(LoginUser? user) async { Future<void> _onLoggedIn(LoginUser? user) async {
var prefs = await SharedPreferences.getInstance();
await prefs.setBool('autoLogin', true);
if (user != null) { if (user != null) {
if (await context.loginRepository().isRegistrationRequired(user)) { if (await context.loginRepository().isRegistrationRequired(user)) {
widget.navRegistration.call(); widget.navRegistration.call();

View file

@ -162,7 +162,6 @@ class LoginPhoneNumberVerifyState extends State<LoginPhoneNumberVerify>
), ),
); );
}, },
onAutoLogin: (user) => widget.onLogin.call(user),
); );
}, },
), ),

View file

@ -83,8 +83,7 @@ class LoginSocialButtons extends StatelessWidget {
interactionType: SocialInteractionType.Login, interactionType: SocialInteractionType.Login,
), ),
); );
var prefs = await SharedPreferences.getInstance();
await prefs.setBool('autoLogin', true);
if (user != null) { if (user != null) {
if (await repository.isRegistrationRequired(user)) { if (await repository.isRegistrationRequired(user)) {
navigateRegistration(); navigateRegistration();