1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-30 00:36:28 +00:00
comunicmobile/lib/ui/routes/forgot_password_route.dart

263 lines
8.1 KiB
Dart
Raw Normal View History

2020-05-03 12:22:06 +00:00
import 'package:comunic/helpers/account_helper.dart';
2020-05-03 14:55:00 +00:00
import 'package:comunic/ui/routes/password_reset_route.dart';
2020-05-03 12:33:26 +00:00
import 'package:comunic/ui/widgets/dialogs/cancel_dialog_button.dart';
2021-04-17 09:46:57 +00:00
import 'package:comunic/ui/widgets/login_route_container.dart';
2020-05-03 12:22:06 +00:00
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';
2020-05-03 13:35:07 +00:00
import 'package:flutter/rendering.dart';
2020-05-03 12:22:06 +00:00
/// Reset password route
///
/// @author Pierre Hubert
2021-02-18 17:28:57 +00:00
class ForgotPasswordRoute extends StatelessWidget {
2020-05-03 12:22:06 +00:00
@override
Widget build(BuildContext context) {
2021-04-17 09:46:57 +00:00
return LoginRouteContainer(
2021-04-17 09:19:18 +00:00
child: 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()),
),
2020-05-03 13:48:10 +00:00
),
2020-05-03 12:22:06 +00:00
),
),
);
}
}
2020-05-03 13:35:07 +00:00
enum _SelectedOption { NONE, SECURITY_QUESTIONS }
2020-05-03 12:33:26 +00:00
2020-05-03 12:22:06 +00:00
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;
2020-05-03 12:33:26 +00:00
var _selectedOption = _SelectedOption.NONE;
2020-05-03 12:22:06 +00:00
void _setLoading(bool loading) => setState(() => _loading = loading);
2020-05-03 13:35:07 +00:00
/// Step 3b - Answer security questions
List<String> _questions;
2021-03-13 14:28:34 +00:00
var _questionsControllers = <TextEditingController>[];
2020-05-03 13:35:07 +00:00
2020-05-03 13:48:10 +00:00
List<String> get _answers =>
_questionsControllers.map((f) => f.text).toList();
bool get _canSubmitAnswers => _answers.every((f) => f.isNotEmpty);
2020-05-03 12:22:06 +00:00
@override
Widget build(BuildContext context) {
if (_loading) return buildCenteredProgressBar();
if (_emailAddress == null) return _buildEnterEmailAddressScreen();
2020-05-03 12:33:26 +00:00
switch (_selectedOption) {
case _SelectedOption.NONE:
return _buildOptionsScreen();
2020-05-03 13:35:07 +00:00
case _SelectedOption.SECURITY_QUESTIONS:
return _buildSecurityQuestionsScreen();
2020-05-03 12:33:26 +00:00
}
2020-05-03 14:55:00 +00:00
throw Exception("Unreachable statement!");
2020-05-03 12:22:06 +00:00
}
Widget _buildEnterEmailAddressScreen() {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
2020-05-03 12:33:26 +00:00
Text(tr("Please enter your email address to reset your password:")),
2020-05-03 12:22:06 +00:00
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");
2021-02-18 17:28:57 +00:00
showSimpleSnack(context,
tr("An error occurred while checking your recovery options !"));
2020-05-03 12:22:06 +00:00
_setLoading(false);
}
}
2020-05-03 12:33:26 +00:00
/// 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:")),
2020-05-03 13:35:07 +00:00
_Spacer(),
2021-03-13 14:14:54 +00:00
OutlinedButton.icon(
2021-04-17 07:15:56 +00:00
onPressed: _openSendEmailDialog,
icon: Icon(Icons.email),
label: Flexible(
child: Text(tr("Send us an email to ask for help")))),
2020-05-03 13:35:07 +00:00
_Spacer(visible: _hasSecurityQuestions),
_hasSecurityQuestions
2021-03-13 14:14:54 +00:00
? OutlinedButton.icon(
2020-05-03 13:35:07 +00:00
onPressed: _loadSecurityQuestions,
icon: Icon(Icons.help_outline),
label: Text(tr("Answer your security questions")),
)
: Container(),
2020-05-03 12:33:26 +00:00
],
);
/// 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()],
));
}
2020-05-03 13:35:07 +00:00
/// 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())
2021-03-13 14:14:54 +00:00
..add(OutlinedButton(
2020-05-03 13:48:10 +00:00
onPressed: _canSubmitAnswers ? _submitSecurityAnswers : null,
2020-05-03 13:35:07 +00:00
child: Text(tr("Submit")),
)),
);
}
2020-05-03 13:48:10 +00:00
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,
2020-05-03 14:21:36 +00:00
labelText:
tr("Answer %num%", args: {"num": (id + 1).toString()})),
2020-05-03 13:48:10 +00:00
),
_Spacer()
],
2020-05-03 13:35:07 +00:00
);
2020-05-03 13:48:10 +00:00
/// Submit security answers
2020-05-03 14:21:36 +00:00
Future<void> _submitSecurityAnswers() async {
_setLoading(true);
try {
final token = await AccountHelper.checkAnswers(_emailAddress, _answers);
2020-05-03 14:55:00 +00:00
_useResetToken(token);
2020-05-03 14:21:36 +00:00
} catch (e, s) {
print("Could not submit security answers! $e\n$s");
showSimpleSnack(
context, tr("Could not validate these security answers!"));
}
_setLoading(false);
}
2020-05-03 14:55:00 +00:00
/// 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)));
2020-05-03 13:35:07 +00:00
}
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,
);
}
2020-05-03 12:22:06 +00:00
}