From 289bf30a40b46fdc0a337b99516411f4b12106dc Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 30 Apr 2020 18:19:01 +0200 Subject: [PATCH] Can change password --- lib/helpers/settings_helper.dart | 10 ++ lib/ui/dialogs/input_new_password_dialog.dart | 112 ++++++++++++++++++ .../account_security_settings.dart | 21 +++- .../auto_sized_dialog_content_widget.dart | 23 ++++ 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 lib/ui/dialogs/input_new_password_dialog.dart create mode 100644 lib/ui/widgets/auto_sized_dialog_content_widget.dart diff --git a/lib/helpers/settings_helper.dart b/lib/helpers/settings_helper.dart index 0276258..2b05e79 100644 --- a/lib/helpers/settings_helper.dart +++ b/lib/helpers/settings_helper.dart @@ -151,4 +151,14 @@ class SettingsHelper { await APIRequest(uri: "settings/check_password", needLogin: true) .addString("password", password) .execWithThrow(); + + /// Change user password + /// + /// Throws in case of failure + static Future changePassword( + String oldPassword, String newPassword) async => + await APIRequest(uri: "settings/update_password", needLogin: true) + .addString("oldPassword", oldPassword) + .addString("newPassword", newPassword) + .execWithThrow(); } diff --git a/lib/ui/dialogs/input_new_password_dialog.dart b/lib/ui/dialogs/input_new_password_dialog.dart new file mode 100644 index 0000000..6c61ab7 --- /dev/null +++ b/lib/ui/dialogs/input_new_password_dialog.dart @@ -0,0 +1,112 @@ +import 'package:comunic/ui/widgets/auto_sized_dialog_content_widget.dart'; +import 'package:comunic/utils/input_utils.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:flutter/material.dart'; + +/// Ask the user to enter a new password +/// +/// @author Pierre HUBERT + +/// Ask the user to enter a new password +Future showInputNewPassword(BuildContext context) async { + return await showDialog( + context: context, builder: (c) => _InputNewPasswordDialog()); +} + +class _InputNewPasswordDialog extends StatefulWidget { + @override + __InputNewPasswordDialogState createState() => + __InputNewPasswordDialogState(); +} + +class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> { + final _controller1 = TextEditingController(); + final _controller2 = TextEditingController(); + final _focusScopeNode = FocusScopeNode(); + + String get _password => _controller1.text; + + bool get _input1Valid => validatePassword(_password); + + bool get _input2Valid => _controller1.text == _controller2.text; + + bool get _isValid => _input1Valid && _input2Valid; + + void Function() get _submitAction => + _isValid ? () => Navigator.of(context).pop(_password) : null; + + @override + void dispose() { + _focusScopeNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(tr("New password")), + content: AutoSizeDialogContentWidget(child: _buildContent()), + actions: [ + MaterialButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(tr("Cancel").toUpperCase()), + ), + MaterialButton( + onPressed: _submitAction, + child: Text(tr("Confirm").toUpperCase()), + ), + ], + ); + } + + Widget _buildContent() { + return FocusScope( + node: _focusScopeNode, + child: Column( + children: [ + // Input 1 + _buildPasswordField( + controller: _controller1, + label: tr("Your new password"), + errorText: _controller1.text.isNotEmpty && !_input1Valid + ? tr("Invalid password!") + : null, + textInputAction: TextInputAction.next, + onSubmitted: () => _focusScopeNode.nextFocus(), + ), + + // Input 2 + _buildPasswordField( + controller: _controller2, + label: tr("Confirm you new password"), + errorText: _controller2.text.isNotEmpty && !_input2Valid + ? tr("This password is not the same as the other one!") + : null, + textInputAction: TextInputAction.done, + onSubmitted: _submitAction), + ], + ), + ); + } + + Widget _buildPasswordField({ + @required TextEditingController controller, + @required String label, + @required String errorText, + @required TextInputAction textInputAction, + @required void Function() onSubmitted, + }) { + return TextFormField( + textInputAction: textInputAction, + controller: controller, + onChanged: (s) => setState(() {}), + onFieldSubmitted: (s) => onSubmitted(), + obscureText: true, + decoration: InputDecoration( + alignLabelWithHint: true, + labelText: label, + errorText: errorText, + ), + ); + } +} diff --git a/lib/ui/routes/account_settings/account_security_settings.dart b/lib/ui/routes/account_settings/account_security_settings.dart index cd986a2..c73fc26 100644 --- a/lib/ui/routes/account_settings/account_security_settings.dart +++ b/lib/ui/routes/account_settings/account_security_settings.dart @@ -1,5 +1,8 @@ +import 'package:comunic/helpers/settings_helper.dart'; +import 'package:comunic/ui/dialogs/input_new_password_dialog.dart'; import 'package:comunic/ui/dialogs/input_user_password_dialog.dart'; import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; import 'package:flutter/material.dart'; import 'package:settings_ui/settings_ui.dart'; @@ -46,6 +49,22 @@ class __AccountSecuritySettingsScreenBodyState /// Change current user password void _changePassword() async { - final currPassword = await showUserPasswordDialog(context); + try { + final currPassword = await showUserPasswordDialog(context); + + if (currPassword == null) return; + + final newPassword = await showInputNewPassword(context); + + if (newPassword == null) return; + + await SettingsHelper.changePassword(currPassword, newPassword); + + showSimpleSnack( + context, tr("Your password has been successfully changed!")); + } catch (e, stack) { + print("Could not update current user password! $e\n$stack"); + showSimpleSnack(context, tr("Could not update password!")); + } } } diff --git a/lib/ui/widgets/auto_sized_dialog_content_widget.dart b/lib/ui/widgets/auto_sized_dialog_content_widget.dart new file mode 100644 index 0000000..53ab723 --- /dev/null +++ b/lib/ui/widgets/auto_sized_dialog_content_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +/// Widget that can be used to build dialog content +/// +/// @author Pierre Hubert +class AutoSizeDialogContentWidget extends StatelessWidget { + final Widget child; + + const AutoSizeDialogContentWidget({ + Key key, + @required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: + BoxConstraints(maxHeight: MediaQuery.of(context).size.height - 50), + child: SingleChildScrollView( + child: child, + )); + } +}