diff --git a/lib/ui/routes/create_account_route.dart b/lib/ui/routes/create_account_route.dart index 683b896..989dda7 100644 --- a/lib/ui/routes/create_account_route.dart +++ b/lib/ui/routes/create_account_route.dart @@ -1,13 +1,7 @@ -import 'package:comunic/helpers/account_helper.dart'; -import 'package:comunic/helpers/server_config_helper.dart'; -import 'package:comunic/models/new_account.dart'; -import 'package:comunic/ui/widgets/new_password_input_widget.dart'; -import 'package:comunic/utils/input_utils.dart'; +import 'package:comunic/ui/widgets/create_account_widget.dart'; import 'package:comunic/utils/intl_utils.dart'; -import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; /// Create account route /// @@ -32,222 +26,9 @@ class _CreateAccountRouteBody extends StatefulWidget { } class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> { - final _firstNameController = TextEditingController(); - final _lastNameController = TextEditingController(); - final _emailController = TextEditingController(); - final _passwordInputKey = GlobalKey(); - final _verifyPasswordController = TextEditingController(); - bool _acceptedTOS = false; - - bool _isCreating = false; - CreateAccountResult _createAccountResult; - - bool _showErrors = false; - - bool get _isFirstNameValid => - _firstNameController.text.length >= - srvConfig.accountInformationPolicy.minFirstNameLength; - - bool get _isLastNameValid => - _lastNameController.text.length >= - srvConfig.accountInformationPolicy.minLastNameLength; - - bool get _isEmailValid => validateEmail(_emailController.text); - - bool get _isPasswordValid => _passwordInputKey.currentState.valid; - - bool get _isPasswordConfirmationValid => - _passwordInputKey.currentState != null && - _passwordInputKey.currentState.value == _verifyPasswordController.text; - - bool get _isFormValid => - _isFirstNameValid && - _isLastNameValid && - _isEmailValid && - _isPasswordValid && - _isPasswordConfirmationValid && - _acceptedTOS; - - String get errorMessage => _createAccountResult == - CreateAccountResult.ERROR_EXISTING_EMAIL - ? tr("An account is already associated to this email address!") - : _createAccountResult == CreateAccountResult.ERROR_TOO_MANY_REQUESTS - ? tr( - "Too many accounts have been created from this IP address for now. Please try again later.") - : tr( - "An error occurred while creating your account. Please try again."); - @override - Widget build(BuildContext context) { - if (_isCreating) - return Center( - child: CircularProgressIndicator(), - ); - - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 370), - child: ListView( - children: [ - _createAccountResult != null - ? buildErrorCard(errorMessage) - : Container(), - - // First name - _InputEntry( - controller: _firstNameController, - label: tr("First name"), - onEdited: _updateUI, - icon: Icon(Icons.perm_identity), - maxLength: - srvConfig.accountInformationPolicy.maxFirstNameLength, - error: _showErrors && !_isFirstNameValid - ? tr("Invalid first name!") - : null, - ), - - // Last name - _InputEntry( - controller: _lastNameController, - label: tr("Last name"), - onEdited: _updateUI, - icon: Icon(Icons.perm_identity), - maxLength: srvConfig.accountInformationPolicy.maxLastNameLength, - error: _showErrors && !_isLastNameValid - ? tr("Invalid last name!") - : null, - ), - - // Email address - _InputEntry( - controller: _emailController, - label: tr("Email address"), - onEdited: _updateUI, - icon: Icon(Icons.email), - keyboard: TextInputType.emailAddress, - error: _showErrors && !_isEmailValid - ? tr("Invalid email address!") - : null, - ), - - // Password - NewPasswordInputWidget( - key: _passwordInputKey, - label: tr("Password"), - onEdited: _updateUI, - icon: Icon(Icons.lock), - user: UserInfoForPassword( - firstName: _firstNameController.text, - lastName: _lastNameController.text, - email: _emailController.text, - ), - ), - - // Verify password - _InputEntry( - controller: _verifyPasswordController, - label: tr("Confirm your password"), - onEdited: _updateUI, - icon: Icon(Icons.lock_outline), - isPassword: true, - error: _showErrors && !_isPasswordConfirmationValid - ? tr("The password and its confirmation do not match!") - : null, - ), - - // TOS - CheckboxListTile( - title: - Text(tr("I have read and accepted the Terms Of Service.")), - value: _acceptedTOS, - onChanged: (b) { - _acceptedTOS = b; - _updateUI(); - }, - subtitle: _showErrors && !_acceptedTOS - ? Text( - tr("You must accept the Terms Of Service to continue."), - style: TextStyle(color: Colors.redAccent), - ) - : null, - controlAffinity: ListTileControlAffinity.leading, - secondary: IconButton( - icon: Icon(Icons.open_in_new), - onPressed: _openTOS, - ), - ), - - // Submit button - Center( - child: ElevatedButton( - onPressed: _submitForm, - child: Text("Submit"), - ), - ), - ], - ), - ), - ), - ); - } - - void _updateUI() { - setState(() { - // Nothing yet - }); - } - - void _submitForm() async { - if (!_isFormValid) { - _showErrors = true; - _updateUI(); - return; - } - - setState(() { - _isCreating = true; - }); - - final result = await AccountHelper().createAccount(NewAccount( - firstName: _firstNameController.text, - lastName: _lastNameController.text, - email: _emailController.text, - password: _passwordInputKey.currentState.value, - )); - - setState(() { - _isCreating = false; - }); - - if (result != CreateAccountResult.SUCCESS) { - _createAccountResult = result; - _showCreateAccountError(); - _updateUI(); - return; - } - - _accountCreated(); - } - - void _openTOS() => launch(ServerConfigurationHelper.config.termsURL); - - void _showCreateAccountError() async { - await showCupertinoDialog( - context: context, - builder: (c) => CupertinoAlertDialog( - title: Text(tr("Error while creating your account")), - content: Text(errorMessage), - actions: [ - CupertinoButton( - child: Text(tr("Ok").toUpperCase()), - onPressed: () => Navigator.of(context).pop(), - ) - ], - ), - ); - } + Widget build(BuildContext context) => + CreateAccountWidget(onCreated: _accountCreated); void _accountCreated() async { await showCupertinoDialog( @@ -266,47 +47,3 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> { Navigator.of(context).pop(); } } - -class _InputEntry extends StatelessWidget { - final TextEditingController controller; - final String label; - final VoidCallback onEdited; - final bool isPassword; - final String error; - final Widget icon; - final TextInputType keyboard; - final int maxLength; - - const _InputEntry({ - Key key, - @required this.controller, - @required this.label, - @required this.onEdited, - this.isPassword = false, - this.error, - this.icon, - this.keyboard, - this.maxLength, - }) : assert(controller != null), - assert(label != null), - assert(onEdited != null), - assert(isPassword != null), - super(key: key); - - @override - Widget build(BuildContext context) { - return TextField( - controller: controller, - onChanged: (s) => onEdited(), - keyboardType: keyboard, - obscureText: isPassword, - maxLength: maxLength, - decoration: InputDecoration( - alignLabelWithHint: true, - errorText: error, - labelText: label, - icon: icon, - ), - ); - } -} diff --git a/lib/ui/widgets/create_account_widget.dart b/lib/ui/widgets/create_account_widget.dart new file mode 100644 index 0000000..a928111 --- /dev/null +++ b/lib/ui/widgets/create_account_widget.dart @@ -0,0 +1,288 @@ +import 'package:comunic/helpers/account_helper.dart'; +import 'package:comunic/helpers/server_config_helper.dart'; +import 'package:comunic/models/new_account.dart'; +import 'package:comunic/utils/input_utils.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'new_password_input_widget.dart'; + +/// Create account widget +/// +/// @author Pierre Hubert + +class CreateAccountWidget extends StatefulWidget { + final void Function() onCreated; + + const CreateAccountWidget({Key key, @required this.onCreated}) + : super(key: key); + + @override + _CreateAccountWidgetState createState() => _CreateAccountWidgetState(); +} + +class _CreateAccountWidgetState extends State { + final _firstNameController = TextEditingController(); + final _lastNameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordInputKey = GlobalKey(); + final _verifyPasswordController = TextEditingController(); + bool _acceptedTOS = false; + + bool _isCreating = false; + CreateAccountResult _createAccountResult; + + bool _showErrors = false; + + bool get _isFirstNameValid => + _firstNameController.text.length >= + srvConfig.accountInformationPolicy.minFirstNameLength; + + bool get _isLastNameValid => + _lastNameController.text.length >= + srvConfig.accountInformationPolicy.minLastNameLength; + + bool get _isEmailValid => validateEmail(_emailController.text); + + bool get _isPasswordValid => _passwordInputKey.currentState.valid; + + bool get _isPasswordConfirmationValid => + _passwordInputKey.currentState != null && + _passwordInputKey.currentState.value == _verifyPasswordController.text; + + bool get _isFormValid => + _isFirstNameValid && + _isLastNameValid && + _isEmailValid && + _isPasswordValid && + _isPasswordConfirmationValid && + _acceptedTOS; + + String get errorMessage => _createAccountResult == + CreateAccountResult.ERROR_EXISTING_EMAIL + ? tr("An account is already associated to this email address!") + : _createAccountResult == CreateAccountResult.ERROR_TOO_MANY_REQUESTS + ? tr( + "Too many accounts have been created from this IP address for now. Please try again later.") + : tr( + "An error occurred while creating your account. Please try again."); + + @override + Widget build(BuildContext context) { + if (_isCreating) + return Center( + child: CircularProgressIndicator(), + ); + + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 370), + child: ListView( + children: [ + _createAccountResult != null + ? buildErrorCard(errorMessage) + : Container(), + + // First name + _InputEntry( + controller: _firstNameController, + label: tr("First name"), + onEdited: _updateUI, + icon: Icon(Icons.perm_identity), + maxLength: + srvConfig.accountInformationPolicy.maxFirstNameLength, + error: _showErrors && !_isFirstNameValid + ? tr("Invalid first name!") + : null, + ), + + // Last name + _InputEntry( + controller: _lastNameController, + label: tr("Last name"), + onEdited: _updateUI, + icon: Icon(Icons.perm_identity), + maxLength: srvConfig.accountInformationPolicy.maxLastNameLength, + error: _showErrors && !_isLastNameValid + ? tr("Invalid last name!") + : null, + ), + + // Email address + _InputEntry( + controller: _emailController, + label: tr("Email address"), + onEdited: _updateUI, + icon: Icon(Icons.email), + keyboard: TextInputType.emailAddress, + error: _showErrors && !_isEmailValid + ? tr("Invalid email address!") + : null, + ), + + // Password + NewPasswordInputWidget( + key: _passwordInputKey, + label: tr("Password"), + onEdited: _updateUI, + icon: Icon(Icons.lock), + user: UserInfoForPassword( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + email: _emailController.text, + ), + ), + + // Verify password + _InputEntry( + controller: _verifyPasswordController, + label: tr("Confirm your password"), + onEdited: _updateUI, + icon: Icon(Icons.lock_outline), + isPassword: true, + error: _showErrors && !_isPasswordConfirmationValid + ? tr("The password and its confirmation do not match!") + : null, + ), + + // TOS + CheckboxListTile( + title: + Text(tr("I have read and accepted the Terms Of Service.")), + value: _acceptedTOS, + onChanged: (b) { + _acceptedTOS = b; + _updateUI(); + }, + subtitle: _showErrors && !_acceptedTOS + ? Text( + tr("You must accept the Terms Of Service to continue."), + style: TextStyle(color: Colors.redAccent), + ) + : null, + controlAffinity: ListTileControlAffinity.leading, + secondary: IconButton( + icon: Icon(Icons.open_in_new), + onPressed: _openTOS, + ), + ), + + // Submit button + Center( + child: ElevatedButton( + onPressed: _submitForm, + child: Text(tr("Create account")), + ), + ), + ], + ), + ), + ), + ); + } + + void _updateUI() { + setState(() { + // Nothing yet + }); + } + + void _submitForm() async { + if (!_isFormValid) { + _showErrors = true; + _updateUI(); + return; + } + + setState(() { + _isCreating = true; + }); + + final result = await AccountHelper().createAccount(NewAccount( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + email: _emailController.text, + password: _passwordInputKey.currentState.value, + )); + + setState(() { + _isCreating = false; + }); + + if (result != CreateAccountResult.SUCCESS) { + _createAccountResult = result; + _showCreateAccountError(); + _updateUI(); + return; + } + + _accountCreated(); + } + + void _openTOS() => launch(ServerConfigurationHelper.config.termsURL); + + void _showCreateAccountError() async { + await showCupertinoDialog( + context: context, + builder: (c) => CupertinoAlertDialog( + title: Text(tr("Error while creating your account")), + content: Text(errorMessage), + actions: [ + CupertinoButton( + child: Text(tr("Ok").toUpperCase()), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ), + ); + } +} + +class _InputEntry extends StatelessWidget { + final TextEditingController controller; + final String label; + final VoidCallback onEdited; + final bool isPassword; + final String error; + final Widget icon; + final TextInputType keyboard; + final int maxLength; + + const _InputEntry({ + Key key, + @required this.controller, + @required this.label, + @required this.onEdited, + this.isPassword = false, + this.error, + this.icon, + this.keyboard, + this.maxLength, + }) : assert(controller != null), + assert(label != null), + assert(onEdited != null), + assert(isPassword != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + onChanged: (s) => onEdited(), + keyboardType: keyboard, + obscureText: isPassword, + maxLength: maxLength, + decoration: InputDecoration( + alignLabelWithHint: true, + errorText: error, + labelText: label, + icon: icon, + ), + ); + } +}