mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-10-31 10:14:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			260 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:comunic/helpers/account_helper.dart';
 | |
| import 'package:comunic/ui/routes/password_reset_route.dart';
 | |
| import 'package:comunic/ui/widgets/dialogs/cancel_dialog_button.dart';
 | |
| import 'package:comunic/ui/widgets/safe_state.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:flutter/rendering.dart';
 | |
| 
 | |
| /// Reset password route
 | |
| ///
 | |
| /// @author Pierre Hubert
 | |
| 
 | |
| class ForgotPasswordRoute extends StatelessWidget {
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Scaffold(
 | |
|       appBar: AppBar(
 | |
|         title: Text(tr("Password forgotten")),
 | |
|       ),
 | |
|       body: Padding(
 | |
|         padding: const EdgeInsets.all(8.0),
 | |
|         child: Center(
 | |
|           child: ConstrainedBox(
 | |
|             constraints: BoxConstraints(maxWidth: 300),
 | |
|             child: SingleChildScrollView(child: _ResetPasswordBody()),
 | |
|           ),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum _SelectedOption { NONE, SECURITY_QUESTIONS }
 | |
| 
 | |
| class _ResetPasswordBody extends StatefulWidget {
 | |
|   @override
 | |
|   _ResetPasswordBodyState createState() => _ResetPasswordBodyState();
 | |
| }
 | |
| 
 | |
| class _ResetPasswordBodyState extends SafeState<_ResetPasswordBody> {
 | |
|   var _loading = false;
 | |
| 
 | |
|   /// Step 1 - check email address
 | |
|   String _emailAddress;
 | |
|   final _emailController = TextEditingController();
 | |
| 
 | |
|   String get _inputEmail => _emailController.text;
 | |
| 
 | |
|   bool get _isEmailValid =>
 | |
|       _inputEmail.isNotEmpty && validateEmail(_inputEmail);
 | |
| 
 | |
|   /// Step 2 - Offer options
 | |
|   bool _hasSecurityQuestions;
 | |
| 
 | |
|   var _selectedOption = _SelectedOption.NONE;
 | |
| 
 | |
|   void _setLoading(bool loading) => setState(() => _loading = loading);
 | |
| 
 | |
|   /// Step 3b - Answer security questions
 | |
|   List<String> _questions;
 | |
|   var _questionsControllers = <TextEditingController>[];
 | |
| 
 | |
|   List<String> get _answers =>
 | |
|       _questionsControllers.map((f) => f.text).toList();
 | |
| 
 | |
|   bool get _canSubmitAnswers => _answers.every((f) => f.isNotEmpty);
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     if (_loading) return buildCenteredProgressBar();
 | |
| 
 | |
|     if (_emailAddress == null) return _buildEnterEmailAddressScreen();
 | |
| 
 | |
|     switch (_selectedOption) {
 | |
|       case _SelectedOption.NONE:
 | |
|         return _buildOptionsScreen();
 | |
| 
 | |
|       case _SelectedOption.SECURITY_QUESTIONS:
 | |
|         return _buildSecurityQuestionsScreen();
 | |
|     }
 | |
| 
 | |
|     throw Exception("Unreachable statement!");
 | |
|   }
 | |
| 
 | |
|   Widget _buildEnterEmailAddressScreen() {
 | |
|     return Column(
 | |
|       mainAxisSize: MainAxisSize.min,
 | |
|       children: <Widget>[
 | |
|         Text(tr("Please enter your email address to reset your password:")),
 | |
|         TextField(
 | |
|           controller: _emailController,
 | |
|           onChanged: (s) => setState(() {}),
 | |
|           onSubmitted: _isEmailValid ? (s) => _checkEmail() : null,
 | |
|           textInputAction: TextInputAction.done,
 | |
|           keyboardType: TextInputType.emailAddress,
 | |
|           decoration: InputDecoration(
 | |
|             icon: Icon(Icons.email),
 | |
|             alignLabelWithHint: true,
 | |
|             labelText: tr("Email address..."),
 | |
|             suffixIcon: IconButton(
 | |
|               icon: Icon(Icons.check),
 | |
|               onPressed: _isEmailValid ? _checkEmail : null,
 | |
|             ),
 | |
|             errorText: _inputEmail.isEmpty || _isEmailValid
 | |
|                 ? null
 | |
|                 : tr("Invalid email address!"),
 | |
|           ),
 | |
|         ),
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /// Check given email address
 | |
|   void _checkEmail() async {
 | |
|     try {
 | |
|       _setLoading(true);
 | |
| 
 | |
|       // Check if email address exists or not
 | |
|       if (!await AccountHelper.existsMailAccount(_inputEmail)) {
 | |
|         _setLoading(false);
 | |
|         showSimpleSnack(context, tr("Specified email address was not found!"));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       _hasSecurityQuestions =
 | |
|           await AccountHelper.hasSecurityQuestions(_inputEmail);
 | |
| 
 | |
|       // We retain email address only if everything went well
 | |
|       _emailAddress = _inputEmail;
 | |
|       _setLoading(false);
 | |
|     } catch (e, s) {
 | |
|       print("Could not check given email! $e\n$s");
 | |
|       showSimpleSnack(context,
 | |
|           tr("An error occurred while checking your recovery options !"));
 | |
|       _setLoading(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Offer the user the options he has to reset his account
 | |
|   Widget _buildOptionsScreen() => Column(
 | |
|         mainAxisSize: MainAxisSize.min,
 | |
|         crossAxisAlignment: CrossAxisAlignment.stretch,
 | |
|         children: <Widget>[
 | |
|           Text(tr("Here are your options to reset your account:")),
 | |
|           _Spacer(),
 | |
|           OutlinedButton.icon(
 | |
|             onPressed: _openSendEmailDialog,
 | |
|             icon: Icon(Icons.email),
 | |
|             label: Text(tr("Send us an email to ask for help")),
 | |
|           ),
 | |
|           _Spacer(visible: _hasSecurityQuestions),
 | |
|           _hasSecurityQuestions
 | |
|               ? OutlinedButton.icon(
 | |
|                   onPressed: _loadSecurityQuestions,
 | |
|                   icon: Icon(Icons.help_outline),
 | |
|                   label: Text(tr("Answer your security questions")),
 | |
|                 )
 | |
|               : Container(),
 | |
|         ],
 | |
|       );
 | |
| 
 | |
|   /// Show a dialog with our email address
 | |
|   void _openSendEmailDialog() {
 | |
|     showDialog(
 | |
|         context: context,
 | |
|         builder: (c) => AlertDialog(
 | |
|               title: Text("Contact us"),
 | |
|               content: Text(tr("You can reach us at contact@communiquons.org")),
 | |
|               actions: <Widget>[CancelDialogButton()],
 | |
|             ));
 | |
|   }
 | |
| 
 | |
|   /// Load security questions
 | |
|   void _loadSecurityQuestions() async {
 | |
|     _setLoading(true);
 | |
|     try {
 | |
|       _questions = await AccountHelper.getSecurityQuestions(_emailAddress);
 | |
| 
 | |
|       _questionsControllers =
 | |
|           List.generate(_questions.length, (i) => TextEditingController());
 | |
|       _selectedOption = _SelectedOption.SECURITY_QUESTIONS;
 | |
|     } catch (e, s) {
 | |
|       print("Could not load security questions! $e\n$s");
 | |
|       showSimpleSnack(context, tr("Could not load your security questions!"));
 | |
|     }
 | |
|     _setLoading(false);
 | |
|   }
 | |
| 
 | |
|   /// Show a screen to prompt security questions of the user
 | |
|   Widget _buildSecurityQuestionsScreen() {
 | |
|     return Column(
 | |
|       mainAxisSize: MainAxisSize.min,
 | |
|       crossAxisAlignment: CrossAxisAlignment.stretch,
 | |
|       children: <Widget>[]
 | |
|         ..add(Text(tr("Please answer now your security questions:")))
 | |
|         ..add(_Spacer())
 | |
|         ..addAll(List.generate(_questions.length, _buildSecurityQuestionField))
 | |
|         ..add(_Spacer())
 | |
|         ..add(OutlinedButton(
 | |
|           onPressed: _canSubmitAnswers ? _submitSecurityAnswers : null,
 | |
|           child: Text(tr("Submit")),
 | |
|         )),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildSecurityQuestionField(int id) => Column(
 | |
|         children: <Widget>[
 | |
|           Text(_questions[id]),
 | |
|           TextField(
 | |
|             controller: _questionsControllers[id],
 | |
|             onChanged: (s) => setState(() {}),
 | |
|             onSubmitted:
 | |
|                 _canSubmitAnswers ? (s) => _submitSecurityAnswers() : null,
 | |
|             decoration: InputDecoration(
 | |
|                 alignLabelWithHint: false,
 | |
|                 labelText:
 | |
|                     tr("Answer %num%", args: {"num": (id + 1).toString()})),
 | |
|           ),
 | |
|           _Spacer()
 | |
|         ],
 | |
|       );
 | |
| 
 | |
|   /// Submit security answers
 | |
|   Future<void> _submitSecurityAnswers() async {
 | |
|     _setLoading(true);
 | |
|     try {
 | |
|       final token = await AccountHelper.checkAnswers(_emailAddress, _answers);
 | |
| 
 | |
|       _useResetToken(token);
 | |
|     } catch (e, s) {
 | |
|       print("Could not submit security answers! $e\n$s");
 | |
|       showSimpleSnack(
 | |
|           context, tr("Could not validate these security answers!"));
 | |
|     }
 | |
|     _setLoading(false);
 | |
|   }
 | |
| 
 | |
|   /// Call this method whenever the user managed to get a password reset token
 | |
|   void _useResetToken(String token) => Navigator.of(context).pushReplacement(
 | |
|       MaterialPageRoute(builder: (c) => PasswordResetRoute(token: token)));
 | |
| }
 | |
| 
 | |
| class _Spacer extends StatelessWidget {
 | |
|   final bool visible;
 | |
| 
 | |
|   const _Spacer({Key key, this.visible = true})
 | |
|       : assert(visible != null),
 | |
|         super(key: key);
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Container(
 | |
|       height: visible ? 35 : null,
 | |
|     );
 | |
|   }
 | |
| }
 |