289 lines
12 KiB
Dart
289 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import '../../constants/app_assets.dart';
|
||
import '../../constants/app_colors.dart';
|
||
import '../../core/app_router.dart';
|
||
import '../../custome_widgets/custom_button.dart';
|
||
import '../../custome_widgets/custom_textfield.dart';
|
||
import '../blocs/reset_password/reset_password_bloc.dart';
|
||
|
||
class ResetPasswordPage extends StatefulWidget {
|
||
final String email;
|
||
|
||
const ResetPasswordPage({super.key, required this.email});
|
||
|
||
@override
|
||
State<ResetPasswordPage> createState() => _ResetPasswordPageState();
|
||
}
|
||
|
||
class _ResetPasswordPageState extends State<ResetPasswordPage> {
|
||
final _passwordController = TextEditingController();
|
||
final _confirmPasswordController = TextEditingController();
|
||
final _passwordFocusNode = FocusNode();
|
||
final _confirmFocusNode = FocusNode();
|
||
|
||
@override
|
||
void dispose() {
|
||
_passwordController.dispose();
|
||
_confirmPasswordController.dispose();
|
||
_passwordFocusNode.dispose();
|
||
_confirmFocusNode.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _onResetPressed(BuildContext context, ResetPasswordState state) {
|
||
FocusManager.instance.primaryFocus?.unfocus();
|
||
|
||
final password = _passwordController.text;
|
||
final confirmPassword = _confirmPasswordController.text;
|
||
|
||
if (!state.isPasswordValid) {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ResetPasswordErrorToggled(true,
|
||
error: "Please fulfill all password requirements"),
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (password != confirmPassword) {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ResetConfirmPasswordErrorToggled(true,
|
||
error: "Passwords do not match"),
|
||
);
|
||
return;
|
||
}
|
||
|
||
context.read<ResetPasswordBloc>().add(
|
||
ResetPasswordSubmitted(
|
||
emailAddress: widget.email,
|
||
newPassword: password,
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return GestureDetector(
|
||
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
|
||
behavior: HitTestBehavior.translucent,
|
||
child: Scaffold(
|
||
backgroundColor: AppColors.backgroundWhite,
|
||
body: BlocConsumer<ResetPasswordBloc, ResetPasswordState>(
|
||
listener: (context, state) {
|
||
if (state.status == ResetPasswordStatus.success) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text("Password reset successfully!"),
|
||
backgroundColor: AppColors.successGreen,
|
||
),
|
||
);
|
||
Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
AppRouter.login,
|
||
(route) => false,
|
||
);
|
||
} else if (state.status == ResetPasswordStatus.failure) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(state.errorMessage ?? "Reset failed"),
|
||
backgroundColor: Colors.redAccent,
|
||
),
|
||
);
|
||
}
|
||
},
|
||
builder: (context, state) {
|
||
final isLoading = state.status == ResetPasswordStatus.loading;
|
||
|
||
return SafeArea(
|
||
child: Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||
child: Column(
|
||
children: [
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
children: [
|
||
SizedBox(height: 40.h),
|
||
// ===== LOGO SECTION =====
|
||
Center(
|
||
child: Column(
|
||
children: [
|
||
Image.asset(
|
||
AppAssets.appIcon,
|
||
height: 60.h,
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
"Partner’s App",
|
||
style: GoogleFonts.poppins(
|
||
color: AppColors.primaryRed,
|
||
fontSize: 20.sp,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 60.h),
|
||
|
||
// ===== HEADER TEXT =====
|
||
Text(
|
||
"Reset your password",
|
||
textAlign: TextAlign.center,
|
||
style: GoogleFonts.poppins(
|
||
color: AppColors.black,
|
||
fontSize: 24.sp,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
"Almost there — just set your new password",
|
||
textAlign: TextAlign.center,
|
||
style: GoogleFonts.poppins(
|
||
color: AppColors.textGrey,
|
||
fontSize: 16.sp,
|
||
),
|
||
),
|
||
SizedBox(height: 48.h),
|
||
|
||
// ===== NEW PASSWORD FIELD =====
|
||
CustomTextField(
|
||
label: 'New Password',
|
||
hintText: 'Enter new password',
|
||
controller: _passwordController,
|
||
focusNode: _passwordFocusNode,
|
||
prefixIcon: Icons.lock_outline,
|
||
isPassword: true,
|
||
isPasswordVisible: state.isPasswordVisible,
|
||
onTogglePasswordVisibility: () {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ResetPasswordVisibilityToggled(),
|
||
);
|
||
},
|
||
hasError: state.showPasswordError,
|
||
errorText: state.passwordErrorText,
|
||
textInputAction: TextInputAction.next,
|
||
readOnly: isLoading,
|
||
onChanged: (val) {
|
||
context
|
||
.read<ResetPasswordBloc>()
|
||
.add(PasswordChanged(val));
|
||
if (state.showPasswordError) {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ResetPasswordErrorToggled(false));
|
||
}
|
||
},
|
||
),
|
||
SizedBox(height: 20.h),
|
||
|
||
// ===== VALIDATION BOX =====
|
||
Container(
|
||
padding: EdgeInsets.all(16.r),
|
||
decoration: BoxDecoration(
|
||
color: AppColors.bgLightGrey,
|
||
borderRadius: BorderRadius.circular(12.r),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Password must contain:",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13.sp,
|
||
color: AppColors.textGrey,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
SizedBox(height: 12.h),
|
||
_buildValidationRow(
|
||
"At least 8 characters", state.hasMinLength),
|
||
_buildValidationRow(
|
||
"One uppercase letter", state.hasUppercase),
|
||
_buildValidationRow(
|
||
"One lowercase letter", state.hasLowercase),
|
||
_buildValidationRow(
|
||
"One number", state.hasNumber),
|
||
_buildValidationRow(
|
||
"One special character", state.hasSpecial),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 20.h),
|
||
|
||
// ===== CONFIRM PASSWORD FIELD =====
|
||
CustomTextField(
|
||
label: 'Confirm New Password',
|
||
hintText: 'Enter your password',
|
||
controller: _confirmPasswordController,
|
||
focusNode: _confirmFocusNode,
|
||
prefixIcon: Icons.lock_outline,
|
||
isPassword: true,
|
||
isPasswordVisible: state.isConfirmPasswordVisible,
|
||
onTogglePasswordVisibility: () {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ConfirmPasswordVisibilityToggled(),
|
||
);
|
||
},
|
||
hasError: state.showConfirmPasswordError,
|
||
errorText: state.confirmPasswordErrorText,
|
||
textInputAction: TextInputAction.done,
|
||
readOnly: isLoading,
|
||
onChanged: (val) {
|
||
if (state.showConfirmPasswordError) {
|
||
context.read<ResetPasswordBloc>().add(
|
||
const ResetConfirmPasswordErrorToggled(
|
||
false));
|
||
}
|
||
},
|
||
onSubmitted: (_) => _onResetPressed(context, state),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
// ===== RESET BUTTON =====
|
||
CustomButton(
|
||
text: "Reset Password",
|
||
isLoading: isLoading,
|
||
onPressed: () => _onResetPressed(context, state),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildValidationRow(String text, bool isValid) {
|
||
return Padding(
|
||
padding: EdgeInsets.only(bottom: 8.h),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
isValid ? Icons.check_circle : Icons.circle,
|
||
size: 16.sp,
|
||
color: isValid ? AppColors.successGreen : const Color(0xFFD1D1D1),
|
||
),
|
||
SizedBox(width: 10.w),
|
||
Text(
|
||
text,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13.sp,
|
||
color: isValid ? AppColors.successGreen : AppColors.textGrey,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|