diff --git a/assets/icons/password_unvisibility.svg b/assets/icons/password_unvisibility.svg
new file mode 100644
index 0000000..c9d8a2d
--- /dev/null
+++ b/assets/icons/password_unvisibility.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/password_visibility.svg b/assets/icons/password_visibility.svg
new file mode 100644
index 0000000..25c5543
--- /dev/null
+++ b/assets/icons/password_visibility.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/verification_icon.svg b/assets/icons/verification_icon.svg
new file mode 100644
index 0000000..949b9a2
--- /dev/null
+++ b/assets/icons/verification_icon.svg
@@ -0,0 +1,10 @@
+
diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart
index f27cbf6..099c827 100644
--- a/lib/features/auth/bloc/auth_cubit.dart
+++ b/lib/features/auth/bloc/auth_cubit.dart
@@ -204,6 +204,7 @@ class AuthCubit extends Cubit {
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
+
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart
index fd68fb4..00dad95 100644
--- a/lib/features/auth/model/user_model.dart
+++ b/lib/features/auth/model/user_model.dart
@@ -14,6 +14,7 @@ class UserModel {
final bool? isEmailVerified;
final String? regionName;
final String? timeZone;
+ final String? regionUuid;
final bool? isAgreementAccepted;
UserModel({
@@ -24,10 +25,10 @@ class UserModel {
required this.profilePicture,
required this.phoneNumber,
required this.isEmailVerified,
+ required this.regionUuid,
required this.isAgreementAccepted,
required this.regionName, // Add this line
required this.timeZone, // Add this line
-
});
factory UserModel.fromJson(Map json) {
@@ -42,21 +43,23 @@ class UserModel {
isAgreementAccepted: json['isAgreementAccepted'],
regionName: json['region']?['regionName'], // Extract regionName
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
+ regionUuid: json['region']?['uuid'],
);
}
//uuid to json
//from token
- factory UserModel.fromToken(Token token) {
+ factory UserModel.fromToken(Token token) {
Map tempJson = Token.decodeToken(token.accessToken);
return UserModel(
uuid: tempJson['uuid'].toString(),
email: tempJson['email'],
lastName: tempJson['lastName'],
- firstName:tempJson['firstName'] ,
+ firstName: tempJson['firstName'],
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
+ regionUuid: null,
regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'],
);
diff --git a/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart b/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart
new file mode 100644
index 0000000..33995ac
--- /dev/null
+++ b/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart
@@ -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 {
+ bool _isPasswordVisible = false;
+ String otpCode = '';
+ String validate = '';
+ SecurityBloc() : super(PasswordVisibilityState(false)) {
+ on(_onSetPassword);
+ on(_onTogglePasswordVisibility);
+ on(_onStartTimer);
+ on(_onStopTimer);
+ on(_onUpdateTimer);
+ on(verifyCode);
+ on(changePassword);
+ }
+
+ void _onSetPassword(SetPassword event, Emitter 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 emit) {
+ _isPasswordVisible = !_isPasswordVisible;
+ emit(PasswordVisibilityState(_isPasswordVisible));
+ }
+
+ String? passwordValidator(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'Please enter your password';
+ }
+ List 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 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 emit) {
+ _timer?.cancel();
+ emit(TimerState(isButtonEnabled: true, remainingTime: 0));
+ }
+
+ void _onUpdateTimer(UpdateTimerEvent event, Emitter 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 verifyCode(
+ VerifyPassCodeEvent event, Emitter 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 changePassword(
+ ChangePasswordEvent event, Emitter 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());
+ }
+ }
+}
diff --git a/lib/features/menu/view/widgets/securty/bloc/security_event.dart b/lib/features/menu/view/widgets/securty/bloc/security_event.dart
new file mode 100644
index 0000000..352192c
--- /dev/null
+++ b/lib/features/menu/view/widgets/securty/bloc/security_event.dart
@@ -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 {}
diff --git a/lib/features/menu/view/widgets/securty/bloc/security_state.dart b/lib/features/menu/view/widgets/securty/bloc/security_state.dart
new file mode 100644
index 0000000..c0b95bc
--- /dev/null
+++ b/lib/features/menu/view/widgets/securty/bloc/security_state.dart
@@ -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