Password field checks for match with old password from db and check so that new password feild matches with the confirm password field

This commit is contained in:
Arseni
2025-12-24 16:18:52 +03:00
parent 964e90767d
commit 43020f3eb6
7 changed files with 93 additions and 10 deletions

View File

@@ -10,6 +10,8 @@ class ConfirmPasswordField extends StatelessWidget {
required this.newPasswordController, required this.newPasswordController,
required this.missingPasswordError, required this.missingPasswordError,
required this.passwordsDoNotMatchError, required this.passwordsDoNotMatchError,
required this.obscureText,
required this.onToggleVisibility,
}); });
final TextEditingController controller; final TextEditingController controller;
@@ -19,6 +21,8 @@ class ConfirmPasswordField extends StatelessWidget {
final TextEditingController newPasswordController; final TextEditingController newPasswordController;
final String missingPasswordError; final String missingPasswordError;
final String passwordsDoNotMatchError; final String passwordsDoNotMatchError;
final bool obscureText;
final VoidCallback onToggleVisibility;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -26,11 +30,18 @@ class ConfirmPasswordField extends StatelessWidget {
width: fieldWidth, width: fieldWidth,
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
obscureText: true, obscureText: obscureText,
enabled: isEnabled, enabled: isEnabled,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: InputDecoration( decoration: InputDecoration(
labelText: confirmPasswordLabel, labelText: confirmPasswordLabel,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: onToggleVisibility,
icon: Icon(
obscureText ? Icons.visibility_off : Icons.visibility,
),
),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return missingPasswordError; if (value == null || value.isEmpty) return missingPasswordError;

View File

@@ -7,6 +7,8 @@ class PasswordField extends StatelessWidget {
required this.labelText, required this.labelText,
required this.fieldWidth, required this.fieldWidth,
required this.isEnabled, required this.isEnabled,
required this.obscureText,
required this.onToggleVisibility,
required this.validator, required this.validator,
}); });
@@ -14,6 +16,8 @@ class PasswordField extends StatelessWidget {
final String labelText; final String labelText;
final double fieldWidth; final double fieldWidth;
final bool isEnabled; final bool isEnabled;
final bool obscureText;
final VoidCallback onToggleVisibility;
final String? Function(String?) validator; final String? Function(String?) validator;
@override @override
@@ -22,14 +26,21 @@ class PasswordField extends StatelessWidget {
width: fieldWidth, width: fieldWidth,
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
obscureText: true, obscureText: obscureText,
enabled: isEnabled, enabled: isEnabled,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText, labelText: labelText,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: onToggleVisibility,
icon: Icon(
obscureText ? Icons.visibility_off : Icons.visibility,
),
),
), ),
validator: validator, validator: validator,
), ),
); );
} }
} }

View File

@@ -18,6 +18,12 @@ class PasswordFields extends StatelessWidget {
required this.fieldWidth, required this.fieldWidth,
required this.gapSmall, required this.gapSmall,
required this.isEnabled, required this.isEnabled,
required this.showOldPassword,
required this.showNewPassword,
required this.showConfirmPassword,
required this.onToggleOldPassword,
required this.onToggleNewPassword,
required this.onToggleConfirmPassword,
}); });
final TextEditingController oldPasswordController; final TextEditingController oldPasswordController;
@@ -31,6 +37,12 @@ class PasswordFields extends StatelessWidget {
final double fieldWidth; final double fieldWidth;
final double gapSmall; final double gapSmall;
final bool isEnabled; final bool isEnabled;
final bool showOldPassword;
final bool showNewPassword;
final bool showConfirmPassword;
final VoidCallback onToggleOldPassword;
final VoidCallback onToggleNewPassword;
final VoidCallback onToggleConfirmPassword;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -41,6 +53,8 @@ class PasswordFields extends StatelessWidget {
labelText: oldPasswordLabel, labelText: oldPasswordLabel,
fieldWidth: fieldWidth, fieldWidth: fieldWidth,
isEnabled: isEnabled, isEnabled: isEnabled,
obscureText: !showOldPassword,
onToggleVisibility: onToggleOldPassword,
validator: (value) => validator: (value) =>
(value == null || value.isEmpty) ? missingPasswordError : null, (value == null || value.isEmpty) ? missingPasswordError : null,
), ),
@@ -50,6 +64,8 @@ class PasswordFields extends StatelessWidget {
labelText: newPasswordLabel, labelText: newPasswordLabel,
fieldWidth: fieldWidth, fieldWidth: fieldWidth,
isEnabled: isEnabled, isEnabled: isEnabled,
obscureText: !showNewPassword,
onToggleVisibility: onToggleNewPassword,
validator: (value) => validator: (value) =>
(value == null || value.isEmpty) ? missingPasswordError : null, (value == null || value.isEmpty) ? missingPasswordError : null,
), ),
@@ -62,8 +78,10 @@ class PasswordFields extends StatelessWidget {
newPasswordController: newPasswordController, newPasswordController: newPasswordController,
missingPasswordError: missingPasswordError, missingPasswordError: missingPasswordError,
passwordsDoNotMatchError: passwordsDoNotMatchError, passwordsDoNotMatchError: passwordsDoNotMatchError,
obscureText: !showConfirmPassword,
onToggleVisibility: onToggleConfirmPassword,
), ),
], ],
); );
} }
} }

View File

@@ -0,0 +1 @@
enum PasswordFieldType { old, newPassword, confirmPassword }

View File

@@ -0,0 +1 @@
enum VisibilityState { hidden, visible }

View File

@@ -4,6 +4,7 @@ import 'package:pshared/provider/account.dart';
import 'package:pshared/widgets/password/fields.dart'; import 'package:pshared/widgets/password/fields.dart';
import 'package:pshared/utils/snackbar.dart'; import 'package:pshared/utils/snackbar.dart';
import 'package:pweb/models/password_field_type.dart';
import 'package:pweb/providers/password_form.dart'; import 'package:pweb/providers/password_form.dart';
import 'package:pweb/pages/settings/profile/account/password/form/error_text.dart'; import 'package:pweb/pages/settings/profile/account/password/form/error_text.dart';
import 'package:pweb/pages/settings/profile/account/password/form/submit_button.dart'; import 'package:pweb/pages/settings/profile/account/password/form/submit_button.dart';
@@ -65,6 +66,18 @@ class PasswordForm extends StatelessWidget {
fieldWidth: _fieldWidth, fieldWidth: _fieldWidth,
gapSmall: _gapSmall, gapSmall: _gapSmall,
isEnabled: !isFormBusy, isEnabled: !isFormBusy,
showOldPassword:
formProvider.isPasswordVisible(PasswordFieldType.old),
showNewPassword:
formProvider.isPasswordVisible(PasswordFieldType.newPassword),
showConfirmPassword: formProvider
.isPasswordVisible(PasswordFieldType.confirmPassword),
onToggleOldPassword: () =>
formProvider.togglePasswordVisibility(PasswordFieldType.old),
onToggleNewPassword: () => formProvider
.togglePasswordVisibility(PasswordFieldType.newPassword),
onToggleConfirmPassword: () => formProvider
.togglePasswordVisibility(PasswordFieldType.confirmPassword),
), ),
const SizedBox(height: _gapMedium), const SizedBox(height: _gapMedium),
PasswordSubmitButton( PasswordSubmitButton(
@@ -72,10 +85,11 @@ class PasswordForm extends StatelessWidget {
label: savePassword, label: savePassword,
onSubmit: () async { onSubmit: () async {
try { try {
await formProvider.submit( final success = await formProvider.submit(
accountProvider: accountProvider, accountProvider: accountProvider,
errorText: errorText, errorText: errorText,
); );
if (!success) return;
if (!context.mounted) return; if (!context.mounted) return;
notifyUser(context, successText); notifyUser(context, successText);
} catch (e) { } catch (e) {

View File

@@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/account.dart';
import 'package:pweb/models/edit_state.dart'; import 'package:pweb/models/edit_state.dart';
import 'package:pshared/api/responses/error/server.dart';
import 'package:pweb/models/password_field_type.dart';
import 'package:pweb/models/visibility.dart';
class PasswordFormProvider extends ChangeNotifier { class PasswordFormProvider extends ChangeNotifier {
@@ -11,6 +14,11 @@ class PasswordFormProvider extends ChangeNotifier {
final newPasswordController = TextEditingController(); final newPasswordController = TextEditingController();
final confirmPasswordController = TextEditingController(); final confirmPasswordController = TextEditingController();
final Map<PasswordFieldType, VisibilityState> _visibility = {
PasswordFieldType.old: VisibilityState.hidden,
PasswordFieldType.newPassword: VisibilityState.hidden,
PasswordFieldType.confirmPassword: VisibilityState.hidden,
};
EditState _state = EditState.view; EditState _state = EditState.view;
String _errorText = ''; String _errorText = '';
bool _disposed = false; bool _disposed = false;
@@ -19,6 +27,8 @@ class PasswordFormProvider extends ChangeNotifier {
bool get isSaving => _state == EditState.saving; bool get isSaving => _state == EditState.saving;
String get errorText => _errorText; String get errorText => _errorText;
EditState get state => _state; EditState get state => _state;
bool isPasswordVisible(PasswordFieldType type) =>
_visibility[type] == VisibilityState.visible;
void toggleExpanded() { void toggleExpanded() {
if (_state == EditState.saving) return; if (_state == EditState.saving) return;
@@ -26,12 +36,21 @@ class PasswordFormProvider extends ChangeNotifier {
_setError(''); _setError('');
} }
Future<void> submit({ void togglePasswordVisibility(PasswordFieldType type) {
final current = _visibility[type];
if (current == null) return;
_visibility[type] = current == VisibilityState.hidden
? VisibilityState.visible
: VisibilityState.hidden;
notifyListeners();
}
Future<bool> submit({
required AccountProvider accountProvider, required AccountProvider accountProvider,
required String errorText, required String errorText,
}) async { }) async {
final currentForm = formKey.currentState; final currentForm = formKey.currentState;
if (currentForm == null || !currentForm.validate()) return; if (currentForm == null || !currentForm.validate()) return false;
_setState(EditState.saving); _setState(EditState.saving);
_setError(''); _setError('');
@@ -45,14 +64,22 @@ class PasswordFormProvider extends ChangeNotifier {
oldPasswordController.clear(); oldPasswordController.clear();
newPasswordController.clear(); newPasswordController.clear();
confirmPasswordController.clear(); confirmPasswordController.clear();
_setState(EditState.view);
return true;
} catch (e) { } catch (e) {
_setError(errorText); _setError(_errorMessageForException(e, errorText));
rethrow;
} finally {
_setState(EditState.edit); _setState(EditState.edit);
rethrow;
} }
} }
String _errorMessageForException(Object exception, String fallback) {
if (exception is ErrorResponse && exception.details.isNotEmpty) {
return exception.details;
}
return fallback;
}
void _setState(EditState value) { void _setState(EditState value) {
if (_state == value || _disposed) return; if (_state == value || _disposed) return;
_state = value; _state = value;