change_password

This commit is contained in:
mohammad
2024-12-30 16:51:35 +03:00
parent 33d2bbc91f
commit 8be05fbd91
16 changed files with 864 additions and 33 deletions

View File

@ -0,0 +1,207 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/services/api/authentication_api.dart';
class SecurityBloc extends Bloc<SecurityEvent, SecurityState> {
bool _isPasswordVisible = false;
String otpCode = '';
String validate = '';
SecurityBloc() : super(PasswordVisibilityState(false)) {
on<SetPassword>(_onSetPassword);
on<TogglePasswordVisibility>(_onTogglePasswordVisibility);
on<StartTimerEvent>(_onStartTimer);
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
on<VerifyPassCodeEvent>(verifyCode);
on<ChangePasswordEvent>(changePassword);
}
void _onSetPassword(SetPassword event, Emitter<SecurityState> emit) {
if (event.password.length < 6) {
emit(PasswordErrorState('Password must be at least 6 characters long.'));
} else {
emit(PasswordSetState('Password successfully set.'));
}
}
void _onTogglePasswordVisibility(
TogglePasswordVisibility event, Emitter<SecurityState> emit) {
_isPasswordVisible = !_isPasswordVisible;
emit(PasswordVisibilityState(_isPasswordVisible));
}
String? passwordValidator(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
List<String> validationErrors = [];
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
validationErrors.add(' - one lowercase letter');
}
if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
validationErrors.add(' - one uppercase letter');
}
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
validationErrors.add(' - one number');
}
if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) {
validationErrors.add(' - one special character');
}
if (value.length < 8) {
validationErrors.add(' - minimum 8 characters');
}
if (validationErrors.isNotEmpty) {
return 'Password must contain at least:\n${validationErrors.join('\n')}';
}
return null;
}
TextEditingController newPassword = TextEditingController();
Timer? _timer;
int _remainingTime = 0;
Future _onStartTimer(
StartTimerEvent event, Emitter<SecurityState> emit) async {
if (_timer != null && _timer!.isActive) {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
_remainingTime = 30;
var res = (await AuthenticationAPI.sendOtp(body: {
'email': HomeCubit.user!.email,
'type': 'VERIFICATION',
if (HomeCubit.user!.regionUuid != null)
'regionUuid': HomeCubit.user!.regionUuid
}));
_remainingTime = res['cooldown'];
} on DioException catch (e) {
if (e.response!.statusCode == 400) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
if (errorMessage == 'User not found') {
validate = 'Invalid Credential';
emit(AuthInitialState());
return 1;
} else {
validate = '';
_remainingTime = errorData['data']['cooldown'] ?? 1;
emit(AuthInitialState());
}
} else {
emit(AuthInitialState());
return 1;
}
emit(AuthInitialState());
} catch (e) {
emit(AuthInitialState());
return 1;
}
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingTime--;
if (_remainingTime <= 0) {
_timer?.cancel();
add(UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
void _onStopTimer(StopTimerEvent event, Emitter<SecurityState> emit) {
_timer?.cancel();
emit(TimerState(isButtonEnabled: true, remainingTime: 0));
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<SecurityState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
String formattedTime(int time) {
final int days = (time / 86400).floor(); // 86400 seconds in a day
final int hours = ((time % 86400) / 3600).floor();
final int minutes = (((time % 86400) % 3600) / 60).floor();
final int seconds = (((time % 86400) % 3600) % 60).floor();
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');
return formattedTime;
}
Future<void> verifyCode(
VerifyPassCodeEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
final response = await AuthenticationAPI.verifyPassCode(body: {
'email': HomeCubit.user!.email!,
'type': 'VERIFICATION',
'otpCode': otpCode
});
//929971
print('response=-=-=-${response}');
if (response['statusCode'] == 200) {
_timer?.cancel();
emit(SuccessForgetState());
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
print('response=-=-=-${event.otpCode}');
final response = await AuthenticationAPI.forgetPassword(
email: HomeCubit.user!.email!,
otpCode: event.otpCode,
password: newPassword.text,
);
//537580
print('response=-=-=-${response}');
// if (response['statusCode'] == 200) {
// _timer?.cancel();
// emit(ChangedPassState());
// }
emit(ChangedPassState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
}

View File

@ -0,0 +1,31 @@
abstract class SecurityEvent {}
class SetPassword extends SecurityEvent {
final String password;
SetPassword(this.password);
}
class TogglePasswordVisibility extends SecurityEvent {}
class SubmitEvent extends SecurityEvent {}
class StartTimerEvent extends SecurityEvent {}
class StopTimerEvent extends SecurityEvent {}
class UpdateTimerEvent extends SecurityEvent {
final int remainingTime;
final bool isButtonEnabled;
UpdateTimerEvent(
{required this.remainingTime, required this.isButtonEnabled});
}
class ChangePasswordEvent extends SecurityEvent {
final String otpCode;
ChangePasswordEvent({
required this.otpCode,
});
}
class VerifyPassCodeEvent extends SecurityEvent {}

View File

@ -0,0 +1,44 @@
abstract class SecurityState {}
class InitialState extends SecurityState {}
class PasswordVisibilityState extends SecurityState {
final bool isVisible;
PasswordVisibilityState(this.isVisible);
}
class PasswordSetState extends SecurityState {
final String message;
PasswordSetState(this.message);
}
class PasswordErrorState extends SecurityState {
final String error;
PasswordErrorState(this.error);
}
class AuthTokenLoading extends SecurityState {}
class AuthLoading extends SecurityState {}
class AuthInitialState extends SecurityState {}
class TimerState extends SecurityState {
final bool isButtonEnabled;
final int remainingTime;
TimerState({required this.isButtonEnabled, required this.remainingTime});
@override
List<Object> get props => [isButtonEnabled, remainingTime];
}
class InitialForgetState extends SecurityState {}
class LoadingForgetState extends SecurityState {}
class SuccessForgetState extends SecurityState {}
class ChangedPassState extends SecurityState {}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/verification_code_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class ChangePasswordPage extends StatelessWidget {
const ChangePasswordPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Change Password',
bottomNavBar: SizedBox(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
// In your parent widget or navigator
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider(
create: (_) => SecurityBloc(), // Provide the Bloc
child: const VerificationCodePage(),
),
),
);
},
child: Container(
height: 50,
margin: const EdgeInsets.only(right: 20, left: 20),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
'Get Verification Code',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.onPrimaryColor),
)),
),
),
],
),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 80, bottom: 30),
child: SvgPicture.asset(Assets.verificationIcon),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Account Verification',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'Click here to send a verification ',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(
height: 4,
),
const BodyMedium(
text: 'code to your email: test@test.com',
fontWeight: FontWeight.w400,
fontSize: 16,
),
],
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/change_password_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
@ -23,7 +24,11 @@ class SecurtyView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
InkWell(
onTap: () {},
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const ChangePasswordPage(),
));
},
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,

View File

@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/devices/view/device_settings/update_dialog.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class SetPasswordPage extends StatelessWidget {
String? otpCode;
SetPasswordPage({super.key, this.otpCode});
@override
Widget build(BuildContext context) {
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc(),
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Change Password Successfully '),
),
);
Navigator.of(context).pop();
}
if (state is ChangedPassState) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(
height: 10,
),
BodyLarge(
text: 'Password Changed',
fontWeight: FontWeight.w700,
fontColor:
ColorsManager.switchButton.withOpacity(0.6),
fontSize: 16,
),
const Padding(
padding: EdgeInsets.only(left: 15, right: 15),
child: Divider(
color: ColorsManager.textGray,
),
),
const Padding(
padding: EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 20),
child: Column(
children: [
Center(
child: Text(
'Your password has been',
textAlign: TextAlign.center,
)),
Center(
child: Text(
'successfully updated.',
textAlign: TextAlign.center,
)),
],
),
),
Container(
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.textGray,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
)),
child: InkWell(
onTap: () {
AuthCubit.get(context).logout();
},
child: Padding(
padding:
const EdgeInsets.only(top: 15, bottom: 15),
child: Center(
child: Text(
'Done',
style: TextStyle(
color: ColorsManager.switchButton
.withOpacity(0.6),
fontSize: 14,
fontWeight: FontWeight.w400),
),
),
)),
)
],
),
),
);
},
);
}
}, builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: ListView(
shrinkWrap: true,
children: [
const SizedBox(height: 55),
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Set Password',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const Center(
child: BodyMedium(
text: 'Secure your account with a',
fontWeight: FontWeight.w400,
fontSize: 16,
),
),
const SizedBox(height: 4),
const Center(
child: BodyMedium(
text: 'strong password',
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
if (state is PasswordErrorState) {
return Center(
child: Text(
state.error,
style: const TextStyle(color: Colors.red),
),
);
} else if (state is PasswordSetState) {
return Center(
child: Text(
state.message,
style: const TextStyle(color: Colors.green),
),
);
}
return const SizedBox.shrink();
},
),
PasswordInputField(controller: _bloc.newPassword),
const SizedBox(height: 55),
InkWell(
onTap: () {
_bloc.add(ChangePasswordEvent(otpCode: otpCode.toString()));
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: const Center(
child: Text(
"Done",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
)
],
),
);
}));
}
}
class PasswordInputField extends StatelessWidget {
PasswordInputField({this.controller});
TextEditingController? controller = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
bool isPasswordVisible = false;
if (state is PasswordVisibilityState) {
isPasswordVisible = state.isVisible;
}
return TextField(
controller: controller,
obscureText: !isPasswordVisible,
decoration: InputDecoration(
hintText: 'Password',
suffixIcon: IconButton(
icon: SvgPicture.asset(isPasswordVisible
? Assets.passwordVisibility
: Assets.passwordUnvisibility),
onPressed: () {
context.read<SecurityBloc>().add(TogglePasswordVisibility());
},
),
),
);
},
);
}
void dispose() {
controller!.dispose();
}
}

View File

@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/set_password_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
class VerificationCodePage extends StatelessWidget {
const VerificationCodePage({super.key});
@override
Widget build(BuildContext context) {
String otp = '';
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc()..add(StartTimerEvent()),
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SetPasswordPage(
otpCode: otp,
),
));
}
},
builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: Column(
children: [
const SizedBox(height: 55),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Verification Code',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'We have sent the verification code',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const BodyMedium(
text: 'to ',
fontWeight: FontWeight.w400,
fontSize: 14,
),
BodyMedium(
text: HomeCubit.user!.email!,
fontWeight: FontWeight.w700,
fontSize: 14,
),
],
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
return Center(
child: PinCodeTextField(
hintCharacter: '0',
appContext: context,
length: 6,
mainAxisAlignment: MainAxisAlignment.center,
backgroundColor: Colors.transparent,
enableActiveFill: true,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
activeColor: Colors.white,
selectedColor: Colors.white,
inactiveColor: Colors.white,
inactiveFillColor: Colors.white70,
selectedFillColor: Colors.white70,
activeFillColor: Colors.white,
errorBorderColor: Colors.white,
fieldHeight: 55.0,
fieldWidth: 55.0,
fieldOuterPadding: const EdgeInsets.only(right: 8),
borderRadius: BorderRadius.circular(17),
borderWidth: 1,
),
cursorWidth: 1,
keyboardType: TextInputType.number,
onChanged: (value) {
// Update OTP code in the bloc when user enters a pin
_bloc.otpCode = value;
otp = value;
},
),
);
},
),
const SizedBox(height: 55),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: InkWell(
onTap: state is TimerState &&
!state.isButtonEnabled &&
state.remainingTime != 1
? null
: () {
_bloc.add(StartTimerEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: BoxDecoration(
color: state is TimerState && !state.isButtonEnabled
? ColorsManager.blueButton
: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: Center(
child: Center(
child: Text(
'${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "${_bloc.formattedTime(state.remainingTime)} " : "Resend"}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: state is TimerState &&
!state.isButtonEnabled
? Colors.white
: ColorsManager.onPrimaryColor,
),
),
),
),
),
)),
const SizedBox(width: 20),
Expanded(
child: InkWell(
onTap: () {
context
.read<SecurityBloc>()
.add(VerifyPassCodeEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
"Verify",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700),
),
),
),
),
),
],
),
],
),
);
},
),
);
}
}