Create account

This commit is contained in:
Hillel Coren 2019-08-26 12:33:58 +03:00
parent 6e8d419e7f
commit 4bc6f65d11
4 changed files with 225 additions and 168 deletions

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
// This version must be updated in tandem with the pubspec version. // This version must be updated in tandem with the pubspec version.
const String kAppVersion = '0.1.47'; const String kAppVersion = '0.1.47';
const String kSiteUrl = 'https://invoiceninja.com'; const String kSiteUrl = 'https://invoiceninja.com';
const String kAppUrl = 'https://app.invoiceninja.com'; const String kAppUrl = 'https://staging.invoiceninja.com';
const String kPrivacyPolicyURL = 'https://www.invoiceninja.com/privacy-policy'; const String kPrivacyPolicyURL = 'https://www.invoiceninja.com/privacy-policy';
const String kTermsOfServiceURL = 'https://www.invoiceninja.com/terms'; const String kTermsOfServiceURL = 'https://www.invoiceninja.com/terms';

View File

@ -77,13 +77,18 @@ class UserLogout implements PersistData, PersistUI {}
class UserSignUpRequest implements StartLoading { class UserSignUpRequest implements StartLoading {
UserSignUpRequest({ UserSignUpRequest({
this.completer, this.completer,
this.firstName,
this.lastName,
this.email, this.email,
this.password, this.password,
this.platform, this.platform,
}); });
final Completer completer; final Completer completer;
final String firstName;
final String lastName;
final String email; final String email;
final String password; final String password;
final String platform; final String platform;

View File

@ -3,6 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart'; import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart';
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
import 'package:invoiceninja_flutter/ui/app/link_text.dart'; import 'package:invoiceninja_flutter/ui/app/link_text.dart';
import 'package:invoiceninja_flutter/ui/app/progress_button.dart'; import 'package:invoiceninja_flutter/ui/app/progress_button.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart'; import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
@ -27,6 +28,8 @@ class LoginView extends StatefulWidget {
class _LoginState extends State<LoginView> { class _LoginState extends State<LoginView> {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
final _emailController = TextEditingController(); final _emailController = TextEditingController();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
final _urlController = TextEditingController(); final _urlController = TextEditingController();
@ -60,6 +63,8 @@ class _LoginState extends State<LoginView> {
@override @override
void dispose() { void dispose() {
_firstNameController.dispose();
_lastNameController.dispose();
_emailController.dispose(); _emailController.dispose();
_passwordController.dispose(); _passwordController.dispose();
_urlController.dispose(); _urlController.dispose();
@ -68,7 +73,7 @@ class _LoginState extends State<LoginView> {
super.dispose(); super.dispose();
} }
void _submitLoginForm() { void _submitSignUpForm() {
final bool isValid = _formKey.currentState.validate(); final bool isValid = _formKey.currentState.validate();
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
@ -103,13 +108,34 @@ class _LoginState extends State<LoginView> {
return; return;
} }
widget.viewModel.onLoginPressed(context, widget.viewModel.onSignUpPressed(
email: _emailController.text, context,
password: _passwordController.text, email: _emailController.text,
url: _isSelfHosted ? _urlController.text : '', password: _passwordController.text,
secret: _isSelfHosted ? _secretController.text : '', firstName: _firstNameController.text,
oneTimePassword: _oneTimePasswordController.text, lastName: _lastNameController.text,
createAccount: _createAccount); );
}
void _submitLoginForm() {
final bool isValid = _formKey.currentState.validate();
setState(() {
_autoValidate = !isValid;
});
if (!isValid) {
return;
}
widget.viewModel.onLoginPressed(
context,
email: _emailController.text,
password: _passwordController.text,
url: _isSelfHosted ? _urlController.text : '',
secret: _isSelfHosted ? _secretController.text : '',
oneTimePassword: _oneTimePasswordController.text,
);
} }
@override @override
@ -161,134 +187,136 @@ class _LoginState extends State<LoginView> {
child: FormCard( child: FormCard(
isResponsive: calculateLayout(context) != AppLayout.mobile, isResponsive: calculateLayout(context) != AppLayout.mobile,
children: <Widget>[ children: <Widget>[
isOneTimePassword if (isOneTimePassword)
? TextFormField( TextFormField(
controller: _oneTimePasswordController, controller: _oneTimePasswordController,
key: ValueKey(localization.oneTimePassword), key: ValueKey(localization.oneTimePassword),
autocorrect: false,
decoration: InputDecoration(
labelText: localization.oneTimePassword),
)
else
Column(
children: <Widget>[
if (_createAccount)
DecoratedFormField(
label: localization.firstName,
controller: _firstNameController,
),
if (_createAccount)
DecoratedFormField(
label: localization.lastName,
controller: _lastNameController,
),
TextFormField(
controller: _emailController,
key: ValueKey(localization.email),
autocorrect: false, autocorrect: false,
decoration: InputDecoration( textInputAction: TextInputAction.next,
labelText: localization.oneTimePassword), decoration:
) InputDecoration(labelText: localization.email),
: Column( keyboardType: TextInputType.emailAddress,
children: <Widget>[ autovalidate: _autoValidate,
TextFormField( validator: (val) => val.isEmpty || val.trim().isEmpty
controller: _emailController, ? localization.pleaseEnterYourEmail
key: ValueKey(localization.email), : null,
autocorrect: false, onFieldSubmitted: (String value) =>
textInputAction: TextInputAction.next, FocusScope.of(context).requestFocus(_focusNode1),
decoration: InputDecoration(
labelText: localization.email),
keyboardType: TextInputType.emailAddress,
autovalidate: _autoValidate,
validator: (val) =>
val.isEmpty || val.trim().isEmpty
? localization.pleaseEnterYourEmail
: null,
onFieldSubmitted: (String value) =>
FocusScope.of(context)
.requestFocus(_focusNode1),
),
TextFormField(
controller: _passwordController,
key: ValueKey(localization.password),
autocorrect: false,
autovalidate: _autoValidate,
decoration: InputDecoration(
labelText: localization.password),
validator: (val) =>
val.isEmpty || val.trim().isEmpty
? localization.pleaseEnterYourPassword
: null,
obscureText: true,
focusNode: _focusNode1,
onFieldSubmitted: (value) =>
_createAccount ? null : _submitLoginForm(),
),
if (_isSelfHosted)
TextFormField(
controller: _urlController,
key: ValueKey(localization.url),
autocorrect: false,
autovalidate: _autoValidate,
decoration: InputDecoration(
labelText: localization.url),
validator: (val) =>
val.isEmpty || val.trim().isEmpty
? localization.pleaseEnterYourUrl
: null,
keyboardType: TextInputType.url,
),
if (_isSelfHosted)
TextFormField(
controller: _secretController,
key: ValueKey(localization.secret),
autocorrect: false,
decoration: InputDecoration(
labelText: localization.secret),
obscureText: true,
),
if (_createAccount)
Padding(
padding: EdgeInsets.only(top: 22),
child: Column(
children: <Widget>[
CheckboxListTile(
onChanged: (value) =>
setState(() => _termsChecked = value),
controlAffinity:
ListTileControlAffinity.leading,
activeColor:
Theme.of(context).accentColor,
value: _termsChecked,
title: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: localization.iAgreeToThe +
' ',
),
LinkTextSpan(
style: linkStyle,
url: kTermsOfServiceURL,
text: localization
.termsOfServiceLink,
),
],
),
),
),
CheckboxListTile(
onChanged: (value) => setState(
() => _privacyChecked = value),
controlAffinity:
ListTileControlAffinity.leading,
activeColor:
Theme.of(context).accentColor,
value: _privacyChecked,
title: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: localization.iAgreeToThe +
' ',
),
LinkTextSpan(
style: linkStyle,
url: kTermsOfServiceURL,
text: localization
.privacyPolicyLink,
),
],
),
),
),
],
),
),
],
), ),
TextFormField(
controller: _passwordController,
key: ValueKey(localization.password),
autocorrect: false,
autovalidate: _autoValidate,
decoration:
InputDecoration(labelText: localization.password),
validator: (val) => val.isEmpty || val.trim().isEmpty
? localization.pleaseEnterYourPassword
: null,
obscureText: true,
focusNode: _focusNode1,
onFieldSubmitted: (value) =>
_createAccount ? null : _submitLoginForm(),
),
if (_isSelfHosted)
TextFormField(
controller: _urlController,
key: ValueKey(localization.url),
autocorrect: false,
autovalidate: _autoValidate,
decoration:
InputDecoration(labelText: localization.url),
validator: (val) =>
val.isEmpty || val.trim().isEmpty
? localization.pleaseEnterYourUrl
: null,
keyboardType: TextInputType.url,
),
if (_isSelfHosted)
TextFormField(
controller: _secretController,
key: ValueKey(localization.secret),
autocorrect: false,
decoration:
InputDecoration(labelText: localization.secret),
obscureText: true,
),
if (_createAccount)
Padding(
padding: EdgeInsets.only(top: 22),
child: Column(
children: <Widget>[
CheckboxListTile(
onChanged: (value) =>
setState(() => _termsChecked = value),
controlAffinity:
ListTileControlAffinity.leading,
activeColor: Theme.of(context).accentColor,
value: _termsChecked,
title: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: localization.iAgreeToThe + ' ',
),
LinkTextSpan(
style: linkStyle,
url: kTermsOfServiceURL,
text: localization.termsOfServiceLink,
),
],
),
),
),
CheckboxListTile(
onChanged: (value) =>
setState(() => _privacyChecked = value),
controlAffinity:
ListTileControlAffinity.leading,
activeColor: Theme.of(context).accentColor,
value: _privacyChecked,
title: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: localization.iAgreeToThe + ' ',
),
LinkTextSpan(
style: linkStyle,
url: kTermsOfServiceURL,
text: localization.privacyPolicyLink,
),
],
),
),
),
],
),
),
],
),
if (viewModel.authState.error != null && if (viewModel.authState.error != null &&
!error.contains(OTP_ERROR)) !error.contains(OTP_ERROR))
Container( Container(
@ -309,7 +337,7 @@ class _LoginState extends State<LoginView> {
? ProgressButton( ? ProgressButton(
isLoading: viewModel.isLoading, isLoading: viewModel.isLoading,
label: localization.signUp.toUpperCase(), label: localization.signUp.toUpperCase(),
onPressed: () => _submitLoginForm(), onPressed: () => _submitSignUpForm(),
) )
: Row( : Row(
children: <Widget>[ children: <Widget>[

View File

@ -39,6 +39,7 @@ class LoginVM {
@required this.isLoading, @required this.isLoading,
@required this.authState, @required this.authState,
@required this.onLoginPressed, @required this.onLoginPressed,
@required this.onSignUpPressed,
@required this.onCancel2FAPressed, @required this.onCancel2FAPressed,
@required this.onGoogleLoginPressed, @required this.onGoogleLoginPressed,
}); });
@ -47,13 +48,22 @@ class LoginVM {
AuthState authState; AuthState authState;
final Function() onCancel2FAPressed; final Function() onCancel2FAPressed;
final Function(BuildContext, final Function(
{@required String email, BuildContext, {
@required String password, @required String email,
@required String url, @required String password,
@required String secret, @required String url,
@required String oneTimePassword, @required String secret,
@required bool createAccount}) onLoginPressed; @required String oneTimePassword,
}) onLoginPressed;
final Function(
BuildContext, {
@required String firstName,
@required String lastName,
@required String email,
@required String password,
}) onSignUpPressed;
final Function(BuildContext, String, String) onGoogleLoginPressed; final Function(BuildContext, String, String) onGoogleLoginPressed;
@ -103,13 +113,36 @@ class LoginVM {
print(error); print(error);
} }
}, },
onLoginPressed: (BuildContext context, onSignUpPressed: (
{@required String email, BuildContext context, {
@required String password, @required String firstName,
@required String url, @required String lastName,
@required String secret, @required String email,
@required String oneTimePassword, @required String password,
@required bool createAccount}) async { }) async {
if (store.state.isLoading) {
return;
}
final Completer<Null> completer = Completer<Null>();
store.dispatch(UserSignUpRequest(
completer: completer,
firstName: firstName.trim(),
lastName: lastName.trim(),
email: email.trim(),
password: password.trim(),
platform: getPlatform(context),
));
completer.future.then((_) => _handleLogin(context));
},
onLoginPressed: (
BuildContext context, {
@required String email,
@required String password,
@required String url,
@required String secret,
@required String oneTimePassword,
}) async {
if (store.state.isLoading) { if (store.state.isLoading) {
return; return;
} }
@ -119,24 +152,15 @@ class LoginVM {
} }
final Completer<Null> completer = Completer<Null>(); final Completer<Null> completer = Completer<Null>();
if (createAccount) { store.dispatch(UserLoginRequest(
store.dispatch(UserSignUpRequest( completer: completer,
completer: completer, email: email.trim(),
email: email.trim(), password: password.trim(),
password: password.trim(), url: url.trim(),
platform: getPlatform(context), secret: secret.trim(),
)); platform: getPlatform(context),
} else { oneTimePassword: oneTimePassword.trim(),
store.dispatch(UserLoginRequest( ));
completer: completer,
email: email.trim(),
password: password.trim(),
url: url.trim(),
secret: secret.trim(),
platform: getPlatform(context),
oneTimePassword: oneTimePassword.trim(),
));
}
completer.future.then((_) => _handleLogin(context)); completer.future.then((_) => _handleLogin(context));
}); });
} }