Create account
This commit is contained in:
parent
6e8d419e7f
commit
4bc6f65d11
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>[
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue