diff --git a/lib/ui/routes/create_account_route.dart b/lib/ui/routes/create_account_route.dart new file mode 100644 index 0000000..aa2dfe0 --- /dev/null +++ b/lib/ui/routes/create_account_route.dart @@ -0,0 +1,205 @@ +import 'package:comunic/utils/input_utils.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:flutter/material.dart'; + +/// Create account route +/// +/// @author Pierre HUBERT + +class CreateAccountRoute extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(tr("Create an account")), + ), + body: _CreateAccountRouteBody(), + ); + } +} + +class _CreateAccountRouteBody extends StatefulWidget { + @override + __CreateAccountRouteBodyState createState() => + __CreateAccountRouteBodyState(); +} + +class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> { + final _firstNameController = TextEditingController(); + final _lastNameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _verifyPasswordController = TextEditingController(); + bool _acceptedTOS = false; + + bool _showErrors = false; + + bool get _isFirstNameValid => _firstNameController.text.length > 3; + + bool get _isLastNameValid => _lastNameController.text.length > 3; + + bool get _isEmailValid => validateEmail(_emailController.text); + + bool get _isPasswordValid => _passwordController.text.length > 3; + + bool get _isPasswordConfirmationValid => + _passwordController.text == _verifyPasswordController.text; + + bool get _isFormValid => + _isFirstNameValid && + _isLastNameValid && + _isEmailValid && + _isPasswordValid && + _isPasswordConfirmationValid && + _acceptedTOS; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + // First name + _InputEntry( + controller: _firstNameController, + label: tr("First name"), + onEdited: _updateUI, + icon: Icon(Icons.perm_identity), + error: _showErrors && !_isFirstNameValid + ? tr("Invalid first name!") + : null, + ), + + // Last name + _InputEntry( + controller: _lastNameController, + label: tr("Last name"), + onEdited: _updateUI, + icon: Icon(Icons.perm_identity), + 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 + _InputEntry( + controller: _passwordController, + label: tr("Password"), + onEdited: _updateUI, + icon: Icon(Icons.lock), + isPassword: true, + error: _showErrors && !_isPasswordValid + ? tr("Invalid password!") + : null, + ), + + // 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, + ), + + // TOSs + CheckboxListTile( + title: Text("I have read and accepted the Term Of Service."), + value: _acceptedTOS, + onChanged: (b) { + _acceptedTOS = b; + _updateUI(); + }, + subtitle: _showErrors && !_acceptedTOS + ? Text( + tr("You must accept the Term Of Service to continue."), + style: TextStyle(color: Colors.redAccent), + ) + : null, + controlAffinity: ListTileControlAffinity.leading, + ), + + // Submit button + Center( + child: RaisedButton( + color: Colors.blue, + textColor: Colors.white, + onPressed: _submitForm, + child: Text("Submit"), + ), + ), + ], + ), + ); + } + + void _updateUI() { + setState(() { + // Nothing yet + }); + } + + void _submitForm() { + if (!_isFormValid) { + _showErrors = true; + _updateUI(); + return; + } + } +} + +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; + + const _InputEntry({ + Key key, + @required this.controller, + @required this.label, + @required this.onEdited, + this.isPassword = false, + this.error, + this.icon, + this.keyboard, + }) : 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, + decoration: InputDecoration( + alignLabelWithHint: true, + errorText: error, + labelText: label, + icon: icon, + ), + ); + } +} diff --git a/lib/ui/routes/login_route.dart b/lib/ui/routes/login_route.dart index 6c88d9f..4160564 100644 --- a/lib/ui/routes/login_route.dart +++ b/lib/ui/routes/login_route.dart @@ -1,5 +1,6 @@ import 'package:comunic/helpers/account_helper.dart'; import 'package:comunic/models/authentication_details.dart'; +import 'package:comunic/ui/routes/create_account_route.dart'; import 'package:comunic/ui/routes/home_route.dart'; import 'package:comunic/utils/input_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; @@ -59,7 +60,12 @@ class _LoginRouteState extends State { }); } - // Build error card + void _openCreateAccountPage() { + Navigator.of(context) + .push(MaterialPageRoute(builder: (c) => CreateAccountRoute())); + } + + /// Build error card Widget _buildErrorCard() { if (_authResult == null) return null; @@ -119,7 +125,15 @@ class _LoginRouteState extends State { child: Text(tr("Sign in")), onPressed: valid ? () => _submitForm(context) : null, ), - ) + ), + + InkWell( + child: Text( + tr("Create an account"), + style: TextStyle(color: Colors.blue), + ), + onTap: () => _openCreateAccountPage(), + ), ], ), ),