From 215483316e6a10d9fcdb731a53ffdf1bdc8d7c8f Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:50:26 +0000 Subject: [PATCH 1/7] Update Settings Panel --- .../screens/dashboard/settings_screen.dart | 96 ++++++++++++------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/lib/src/screens/dashboard/settings_screen.dart b/lib/src/screens/dashboard/settings_screen.dart index 700bda1..449b56d 100644 --- a/lib/src/screens/dashboard/settings_screen.dart +++ b/lib/src/screens/dashboard/settings_screen.dart @@ -39,44 +39,72 @@ class _SettingsScreenState extends State { const SizedBox( height: 20, ), - ListTile( - title: const Text("Personal Details"), - onTap: () async { - EasyLoading.show(status: 'loading...'); - final result = await _viewPersonalDetails(); - if (result.runtimeType == String) { - EasyLoading.showError(result.toString()); - } else { - Navigator.pushNamed( - context, - ViewProfileScreen.kID, - arguments: result, - ); - EasyLoading.dismiss(); - } - }, - tileColor: Colors.blue, + Card( + elevation: 8, + child: ListTile( + leading: const Icon(Icons.person), + title: const Text("Personal Details"), + onTap: () async { + EasyLoading.show(status: 'loading...'); + final result = await _viewPersonalDetails(); + if (result.runtimeType == String) { + EasyLoading.showError(result.toString()); + } else { + Navigator.pushNamed( + context, + ViewProfileScreen.kID, + arguments: result, + ); + EasyLoading.dismiss(); + } + }, + ), ), const SizedBox( height: 20, ), - ListTile( - title: const Text("Logout"), - onTap: () async { - EasyLoading.show(); - final result = await _signOut(); - if (result != null) { - EasyLoading.showError(result); - } else { - Navigator.pushReplacementNamed( - context, - LoginScreen.kID, - ); - EasyLoading.dismiss(); - } - }, - tileColor: Colors.purple, - ) + Card( + elevation: 8, + child: ListTile( + leading: const Icon(Icons.email), + title: const Text("Change Email"), + onTap: () async {}, + ), + ), + const SizedBox( + height: 20, + ), + Card( + elevation: 8, + child: ListTile( + leading: const Icon(Icons.password), + title: const Text("Change Password"), + onTap: () async {}, + ), + ), + const SizedBox( + height: 20, + ), + Card( + elevation: 8, + child: ListTile( + leading: const Icon(Icons.logout), + title: const Text("Logout"), + onTap: () async { + EasyLoading.show(); + final result = await _signOut(); + if (result != null) { + EasyLoading.showError(result); + } else { + Navigator.pushReplacementNamed( + context, + LoginScreen.kID, + ); + EasyLoading.dismiss(); + } + }, + ), + ), ], ), ), From ca6176ee68ed74e61fe9f4dd5ce385de79660a27 Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:50:54 +0000 Subject: [PATCH 2/7] Update App Version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7529372..6742cca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 2.3.0+1 +version: 3.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" From f56955f3f5928413c183132d751eebd6e1ed387d Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:40:37 +0000 Subject: [PATCH 3/7] Updated Password Validator --- lib/src/utilities/form_validators/password_validator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utilities/form_validators/password_validator.dart b/lib/src/utilities/form_validators/password_validator.dart index 6b53701..75521ae 100644 --- a/lib/src/utilities/form_validators/password_validator.dart +++ b/lib/src/utilities/form_validators/password_validator.dart @@ -3,7 +3,7 @@ import 'package:monerate/src/utilities/export.dart'; class PasswordValidator extends Validator { String? validatePassword(String? value) { if (super.presenceDetection(value) == false) { - return 'A Password is required to login'; + return 'A Password is required'; } if (!RegExp(r'^.{6,}$').hasMatch(value!)) { return "Enter a valid password (Minimum 6 chararacters)"; From fd4da39091d8c84c53e95c063a8aba0525d76171 Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:40:50 +0000 Subject: [PATCH 4/7] Updated Password Validator Test --- .../utilities/form_validators/password_validator_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/utilities/form_validators/password_validator_test.dart b/test/src/utilities/form_validators/password_validator_test.dart index 65ad447..979944b 100644 --- a/test/src/utilities/form_validators/password_validator_test.dart +++ b/test/src/utilities/form_validators/password_validator_test.dart @@ -5,7 +5,7 @@ void main() { group('Password Validator: ', () { test('Empty password returns error', () { final result = PasswordValidator().validatePassword(''); - expect(result, "A Password is required to login"); + expect(result, "A Password is required"); }); test('Password less than six characters returns error', () { @@ -22,7 +22,7 @@ void main() { test('Empty passwords returns error', () { final result = PasswordValidator().confirmPassword('', ''); - expect(result, "A Password is required to login"); + expect(result, "A Password is required"); }); test('If passwords are less than 6 characters, return error', () { From 1a558dfb0d1a4c5af502e01a1fa0a3f47b0f0754 Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:41:29 +0000 Subject: [PATCH 5/7] Implemented HP-67 --- lib/src/providers/auth_provider.dart | 19 ++ .../change_email/change_email_screen.dart | 190 ++++++++++++++++++ lib/src/screens/change_email/export.dart | 1 + .../screens/dashboard/settings_screen.dart | 5 +- lib/src/screens/export.dart | 1 + .../utilities/constants/route_constants.dart | 1 + 6 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 lib/src/screens/change_email/change_email_screen.dart create mode 100644 lib/src/screens/change_email/export.dart diff --git a/lib/src/providers/auth_provider.dart b/lib/src/providers/auth_provider.dart index 52c5687..e686470 100644 --- a/lib/src/providers/auth_provider.dart +++ b/lib/src/providers/auth_provider.dart @@ -139,4 +139,23 @@ class AuthProvider { return exceptionsFactory.exceptionCaught()!; } } + + Future changeEmail(String newEmail, String password) async { + try { + user = _auth.currentUser; + final currentEmail = user!.email; + final credential = EmailAuthProvider.credential( + email: currentEmail!, + password: password, + ); + await user! + .reauthenticateWithCredential(credential) + .then((value) => user!.updateEmail(newEmail)); + await user!.sendEmailVerification(); + logout(); + } on FirebaseAuthException catch (e) { + exceptionsFactory = ExceptionsFactory(e.code); + return exceptionsFactory.exceptionCaught()!; + } + } } diff --git a/lib/src/screens/change_email/change_email_screen.dart b/lib/src/screens/change_email/change_email_screen.dart new file mode 100644 index 0000000..0de4d0e --- /dev/null +++ b/lib/src/screens/change_email/change_email_screen.dart @@ -0,0 +1,190 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:monerate/src/providers/export.dart'; +import 'package:monerate/src/screens/export.dart'; +import 'package:monerate/src/utilities/export.dart'; +import 'package:monerate/src/widgets/export.dart'; + +class ChangeEmailScreen extends StatefulWidget { + static const kID = 'change_email_screen'; + const ChangeEmailScreen({Key? key}) : super(key: key); + + @override + _ChangeEmailScreenState createState() => _ChangeEmailScreenState(); +} + +class _ChangeEmailScreenState extends State { + final TextEditingController newEmailController = TextEditingController(); + final TextEditingController confirmEmailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final _formKey = GlobalKey(); + + bool _showPassword = false; + + final AuthProvider authProvider = AuthProvider(); + + @override + void dispose() { + // Clean up controllers when form is disposed + newEmailController.dispose(); + confirmEmailController.dispose(); + passwordController.dispose(); + super.dispose(); + } + + void closeDialogBox() { + return Navigator.pop(context); + } + + Future _updateEmail() async { + return authProvider.changeEmail( + confirmEmailController.text, + passwordController.text, + ); + } + + Future displayConfirmationDialog() { + return customAlertDialog( + context: context, + title: "Confirmation Required", + content: + "By continuing with this action, you will be signed out of the current session. A verification email will be sent to the provided email which must be verified before logging in with your new credentials. Do you wish to continue?", + actions: [ + OutlinedButton( + onPressed: () => closeDialogBox(), + child: const Text( + "Cancel", + ), + ), + ElevatedButton( + onPressed: () async { + EasyLoading.show(status: 'loading...'); + final result = await _updateEmail(); + if (result != null) { + closeDialogBox(); + EasyLoading.showError(result); + } else { + Navigator.pushReplacementNamed( + context, + LoginScreen.kID, + ); + EasyLoading.dismiss(); + } + }, + child: const Text("Update Email"), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + "Change Email Address", + ), + centerTitle: true, + ), + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: newEmailController, + keyboardType: TextInputType.emailAddress, + validator: EmailValidator().validateEmail, + onSaved: (value) { + newEmailController.text = value!; + }, + decoration: const InputDecoration( + hintText: 'New Email Address', + ), + ), + const SizedBox( + height: 20, + ), + TextFormField( + controller: confirmEmailController, + keyboardType: TextInputType.emailAddress, + validator: (value) { + return EmailValidator().confirmEmail( + newEmailController.text, + confirmEmailController.text, + ); + }, + onSaved: (value) { + confirmEmailController.text = value!; + }, + decoration: const InputDecoration( + hintText: 'Confirm New Email Address', + ), + ), + const SizedBox( + height: 20, + ), + TextFormField( + controller: passwordController, + validator: PasswordValidator().validatePassword, + obscureText: !_showPassword, + textInputAction: TextInputAction.done, + onSaved: (password) { + passwordController.text = password!; + }, + decoration: InputDecoration( + hintText: 'Password', + suffixIcon: GestureDetector( + onTap: () => setState(() { + _showPassword = !_showPassword; + }), + child: Icon( + _showPassword + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + FocusScope.of(context).unfocus(); + await displayConfirmationDialog(); + } + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 40, + vertical: 10, + ), + ), + child: const Text( + "Submit", + style: TextStyle( + fontSize: 24, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/change_email/export.dart b/lib/src/screens/change_email/export.dart new file mode 100644 index 0000000..a299f58 --- /dev/null +++ b/lib/src/screens/change_email/export.dart @@ -0,0 +1 @@ +export 'change_email_screen.dart'; diff --git a/lib/src/screens/dashboard/settings_screen.dart b/lib/src/screens/dashboard/settings_screen.dart index 449b56d..f0324e2 100644 --- a/lib/src/screens/dashboard/settings_screen.dart +++ b/lib/src/screens/dashboard/settings_screen.dart @@ -68,7 +68,10 @@ class _SettingsScreenState extends State { child: ListTile( leading: const Icon(Icons.email), title: const Text("Change Email"), - onTap: () async {}, + onTap: () => Navigator.pushNamed( + context, + ChangeEmailScreen.kID, + ), ), ), const SizedBox( diff --git a/lib/src/screens/export.dart b/lib/src/screens/export.dart index bbf0c40..e04d35a 100644 --- a/lib/src/screens/export.dart +++ b/lib/src/screens/export.dart @@ -1,3 +1,4 @@ +export 'change_email/export.dart'; export 'complete_profile/export.dart'; export 'construction_page.dart'; export 'dashboard/export.dart'; diff --git a/lib/src/utilities/constants/route_constants.dart b/lib/src/utilities/constants/route_constants.dart index 5e2d00b..bdbbaf6 100644 --- a/lib/src/utilities/constants/route_constants.dart +++ b/lib/src/utilities/constants/route_constants.dart @@ -17,4 +17,5 @@ Map kRoutes = { const CompleteSupportManagerProfile(), DashboardScreen.kID: (context) => const DashboardScreen(), ViewProfileScreen.kID: (context) => const ViewProfileScreen(), + ChangeEmailScreen.kID: (context) => const ChangeEmailScreen(), }; From ad9833b034e0035a9e625219d9aca02faeb8c35f Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:53:37 +0000 Subject: [PATCH 6/7] Update App Version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 6742cca..472409c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.0.0+1 +version: 3.1.0+1 environment: sdk: ">=2.12.0 <3.0.0" From 7966de6167fb5ee6e68db779ff91b4727851c94a Mon Sep 17 00:00:00 2001 From: Kamran <56407135+k5924@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:55:43 +0000 Subject: [PATCH 7/7] Implemented HP-68 --- lib/src/providers/auth_provider.dart | 18 ++ .../change_password_screen.dart | 201 ++++++++++++++++++ lib/src/screens/change_password/export.dart | 1 + .../screens/dashboard/settings_screen.dart | 5 +- lib/src/screens/export.dart | 1 + .../utilities/constants/route_constants.dart | 1 + pubspec.yaml | 2 +- 7 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 lib/src/screens/change_password/change_password_screen.dart create mode 100644 lib/src/screens/change_password/export.dart diff --git a/lib/src/providers/auth_provider.dart b/lib/src/providers/auth_provider.dart index e686470..ba7ef14 100644 --- a/lib/src/providers/auth_provider.dart +++ b/lib/src/providers/auth_provider.dart @@ -158,4 +158,22 @@ class AuthProvider { return exceptionsFactory.exceptionCaught()!; } } + + Future changePassword(String oldPassword, String newPassword) async { + try { + user = _auth.currentUser; + final currentEmail = user!.email; + final credential = EmailAuthProvider.credential( + email: currentEmail!, + password: oldPassword, + ); + await user! + .reauthenticateWithCredential(credential) + .then((value) => user!.updatePassword(newPassword)); + logout(); + } on FirebaseAuthException catch (e) { + exceptionsFactory = ExceptionsFactory(e.code); + return exceptionsFactory.exceptionCaught()!; + } + } } diff --git a/lib/src/screens/change_password/change_password_screen.dart b/lib/src/screens/change_password/change_password_screen.dart new file mode 100644 index 0000000..2c8a1a6 --- /dev/null +++ b/lib/src/screens/change_password/change_password_screen.dart @@ -0,0 +1,201 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:monerate/src/providers/export.dart'; +import 'package:monerate/src/screens/export.dart'; +import 'package:monerate/src/utilities/export.dart'; +import 'package:monerate/src/widgets/export.dart'; + +class ChangePasswordScreen extends StatefulWidget { + static const kID = "change_password_screen"; + const ChangePasswordScreen({Key? key}) : super(key: key); + + @override + _ChangePasswordScreenState createState() => _ChangePasswordScreenState(); +} + +class _ChangePasswordScreenState extends State { + final TextEditingController oldPasswordController = TextEditingController(); + final TextEditingController newPasswordController = TextEditingController(); + final TextEditingController confirmNewPasswordController = + TextEditingController(); + final _formKey = GlobalKey(); + bool _showPassword = false; + + final AuthProvider authProvider = AuthProvider(); + + void closeDialogBox() { + return Navigator.pop(context); + } + + Future _updatePassword() async { + return authProvider.changePassword( + oldPasswordController.text, + confirmNewPasswordController.text, + ); + } + + Future displayConfirmationDialog() { + return customAlertDialog( + context: context, + title: "Confirmation Required", + content: + "By continuing with this action, you will be signed out of the current session and will be asked to login with the new credentials you have provided. Do you wish to continue?", + actions: [ + OutlinedButton( + onPressed: () => closeDialogBox(), + child: const Text( + "Cancel", + ), + ), + ElevatedButton( + onPressed: () async { + EasyLoading.show(status: 'loading...'); + final result = await _updatePassword(); + if (result != null) { + closeDialogBox(); + EasyLoading.showError(result); + } else { + Navigator.pushReplacementNamed( + context, + LoginScreen.kID, + ); + EasyLoading.dismiss(); + } + }, + child: const Text("Update Email"), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + "Change Password", + ), + centerTitle: true, + ), + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Form( + key: _formKey, + child: Column( + children: [ + const SizedBox( + height: 20, + ), + TextFormField( + controller: oldPasswordController, + validator: PasswordValidator().validatePassword, + obscureText: !_showPassword, + textInputAction: TextInputAction.done, + onSaved: (password) { + oldPasswordController.text = password!; + }, + decoration: InputDecoration( + hintText: 'Old Password', + suffixIcon: GestureDetector( + onTap: () => setState(() { + _showPassword = !_showPassword; + }), + child: Icon( + _showPassword + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + TextFormField( + controller: newPasswordController, + validator: PasswordValidator().validatePassword, + obscureText: !_showPassword, + textInputAction: TextInputAction.done, + onSaved: (password) { + newPasswordController.text = password!; + }, + decoration: InputDecoration( + hintText: 'New Password', + suffixIcon: GestureDetector( + onTap: () => setState(() { + _showPassword = !_showPassword; + }), + child: Icon( + _showPassword + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + TextFormField( + controller: confirmNewPasswordController, + validator: PasswordValidator().validatePassword, + obscureText: !_showPassword, + textInputAction: TextInputAction.done, + onSaved: (password) { + confirmNewPasswordController.text = password!; + }, + decoration: InputDecoration( + hintText: 'Confirm New Password', + suffixIcon: GestureDetector( + onTap: () => setState(() { + _showPassword = !_showPassword; + }), + child: Icon( + _showPassword + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + FocusScope.of(context).unfocus(); + await displayConfirmationDialog(); + } + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 40, + vertical: 10, + ), + ), + child: const Text( + "Submit", + style: TextStyle( + fontSize: 24, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/change_password/export.dart b/lib/src/screens/change_password/export.dart new file mode 100644 index 0000000..9fe08d8 --- /dev/null +++ b/lib/src/screens/change_password/export.dart @@ -0,0 +1 @@ +export 'change_password_screen.dart'; \ No newline at end of file diff --git a/lib/src/screens/dashboard/settings_screen.dart b/lib/src/screens/dashboard/settings_screen.dart index f0324e2..922eef3 100644 --- a/lib/src/screens/dashboard/settings_screen.dart +++ b/lib/src/screens/dashboard/settings_screen.dart @@ -82,7 +82,10 @@ class _SettingsScreenState extends State { child: ListTile( leading: const Icon(Icons.password), title: const Text("Change Password"), - onTap: () async {}, + onTap: () => Navigator.pushNamed( + context, + ChangePasswordScreen.kID, + ), ), ), const SizedBox( diff --git a/lib/src/screens/export.dart b/lib/src/screens/export.dart index e04d35a..0d9afb6 100644 --- a/lib/src/screens/export.dart +++ b/lib/src/screens/export.dart @@ -1,4 +1,5 @@ export 'change_email/export.dart'; +export 'change_password/export.dart'; export 'complete_profile/export.dart'; export 'construction_page.dart'; export 'dashboard/export.dart'; diff --git a/lib/src/utilities/constants/route_constants.dart b/lib/src/utilities/constants/route_constants.dart index bdbbaf6..b2999f5 100644 --- a/lib/src/utilities/constants/route_constants.dart +++ b/lib/src/utilities/constants/route_constants.dart @@ -18,4 +18,5 @@ Map kRoutes = { DashboardScreen.kID: (context) => const DashboardScreen(), ViewProfileScreen.kID: (context) => const ViewProfileScreen(), ChangeEmailScreen.kID: (context) => const ChangeEmailScreen(), + ChangePasswordScreen.kID: (context) => const ChangePasswordScreen(), }; diff --git a/pubspec.yaml b/pubspec.yaml index 472409c..565244d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.1.0+1 +version: 3.2.0+1 environment: sdk: ">=2.12.0 <3.0.0"