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_string.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, ), SizedBox(height: 10), // 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, ), ), SizedBox(height: 10), // 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; } widget.onCreated(); } void _openTOS() => launchUrlString(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, }) : super(key: key); @override Widget build(BuildContext context) { return TextField( controller: controller, onChanged: (s) => onEdited(), keyboardType: keyboard, obscureText: isPassword, maxLength: maxLength, decoration: InputDecoration( counterText: "", alignLabelWithHint: true, errorText: error, labelText: label, icon: icon, ), ); } }