From eca1ac5f527f39c21e8f434f63bf4f95ca561dff Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Fri, 18 Jul 2025 23:51:18 +0530 Subject: [PATCH] [client] Change Password [#18] --- .../requests/change_password_request.dart | 21 +++++ client/lib/ui/profile/profile_dialog.dart | 89 ++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 client/lib/libphylum/requests/change_password_request.dart diff --git a/client/lib/libphylum/requests/change_password_request.dart b/client/lib/libphylum/requests/change_password_request.dart new file mode 100644 index 00000000..00734a64 --- /dev/null +++ b/client/lib/libphylum/requests/change_password_request.dart @@ -0,0 +1,21 @@ +import 'dart:typed_data'; + +import 'package:http/http.dart'; +import 'package:offtheline/offtheline.dart'; + +class ChangePasswordRequest extends ApiRequest { + final String email; + final String oldPassword; + final String newPassword; + + const ChangePasswordRequest({required this.email, required this.oldPassword, required this.newPassword}); + + @override + BaseRequest createRequest(ApiClient api, {Uint8List? data}) { + final uri = api.createUri('/api/v1/auth/password/change'); + return MultipartRequest('post', uri) + ..fields['email'] = email + ..fields['old_password'] = oldPassword + ..fields['new_password'] = newPassword; + } +} diff --git a/client/lib/ui/profile/profile_dialog.dart b/client/lib/ui/profile/profile_dialog.dart index bb8c8a3e..b407259d 100644 --- a/client/lib/ui/profile/profile_dialog.dart +++ b/client/lib/ui/profile/profile_dialog.dart @@ -4,6 +4,7 @@ import 'package:offtheline/offtheline.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/libphylum/requests/update_user_name_request.dart'; +import 'package:phylum/libphylum/requests/update_user_password_request.dart'; import 'package:phylum/libphylum/responses/responses.dart'; import 'package:phylum/ui/profile/profile_view.dart'; import 'package:phylum/util/dialogs.dart'; @@ -74,12 +75,14 @@ class ProfileDialog extends StatelessWidget { ), subtitle: Text(context.read().apiClient.serverUrl.host), ), - Divider(thickness: 2), - const ListTile( + Divider(thickness: 2, height: 2), + ListTile( visualDensity: VisualDensity.compact, leading: Icon(Icons.password), title: Text('Change Password'), + onTap: () => changePassword(context), ), + Divider(thickness: 1, height: 1), ListTile( visualDensity: VisualDensity.compact, leading: const Icon(Icons.logout), @@ -176,4 +179,86 @@ class ProfileDialog extends StatelessWidget { } } } + + void changePassword(BuildContext context) async { + final account = context.read(); + bool passwordsVisible = false; + String oldPassword = ''; + String newPassword = ''; + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: Text('Change Password'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: InputDecoration( + label: Text('Current Password'), + ), + obscureText: !passwordsVisible, + enableSuggestions: false, + autocorrect: false, + autofocus: true, + autofillHints: const [AutofillHints.password], + keyboardType: TextInputType.visiblePassword, + onChanged: (value) => oldPassword = value, + ), + TextField( + decoration: InputDecoration( + label: Text('New Password'), + ), + obscureText: !passwordsVisible, + enableSuggestions: false, + autocorrect: false, + autofocus: true, + autofillHints: const [AutofillHints.password], + keyboardType: TextInputType.visiblePassword, + onChanged: (value) => newPassword = value, + ), + Row( + children: [ + Checkbox(value: passwordsVisible, onChanged: (value) => setState(() => passwordsVisible = value!)), + const Text('Show Passwords') + ], + ) + ], + ), + actions: [ + TextButton( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error), + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(false), + ), + ElevatedButton( + autofocus: true, + child: Text('OK'), + onPressed: () async { + showProgressDialog(context, title: 'Changing Password'); + + final response = await account.apiClient.sendRequest( + ChangePasswordRequest(email: account.user.email, oldPassword: oldPassword, newPassword: newPassword), + (request, response) => parseJsonMapResponse(response, EmptyResponse.fromResponse), + ); + + if (context.mounted) { + Navigator.of(context).pop(); + if (response is ApiErrorResponse) { + showAlertDialog(context, message: response.description); + } else if (response is ApiUnreachableError) { + showAlertDialog(context, message: 'Server Unreachable'); + } else { + Navigator.of(context).pop(); + showAlertDialog(context, message: 'Password Changed'); + } + } + }, + // onPressed: () => Navigator.of(context).pop(true), + ), + ], + ), + ), + ); + } }