import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:syncrow_app/features/auth/model/login_with_email_model.dart'; import 'package:syncrow_app/features/auth/model/signup_model.dart'; import 'package:syncrow_app/features/auth/model/token.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/services/api/authentication_api.dart'; import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; part 'auth_state.dart'; class AuthCubit extends Cubit { AuthCubit() : super(AuthInitial()); static AuthCubit get(context) => BlocProvider.of(context); final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); String fullName = ''; String email = ''; String forgetPasswordEmail = ''; String signUpPassword = ''; String newPassword = ''; String maskedEmail = ''; String otpCode = ''; final loginFormKey = GlobalKey(); final signUpFormKey = GlobalKey(); final checkEmailFormKey = GlobalKey(); final createNewPasswordKey = GlobalKey(); bool isPasswordVisible = false; bool showValidationMessage = false; void changePasswordVisibility() { isPasswordVisible = !isPasswordVisible; emit(AuthPasswordVisibilityChanged()); } bool agreeToTerms = false; void changeAgreeToTerms() { agreeToTerms = !agreeToTerms; emit(AuthAgreeToTermsChanged()); } static UserModel? user; static Token token = Token.emptyConstructor(); setOtpCode(String value) { otpCode = value; } /////////////////////////////////////VALIDATORS///////////////////////////////////// String? passwordValidator(String? value) { List errors = []; if (value == null || value.isEmpty) { errors.add("Please enter your password"); } if (value != null && value.length < 8) { errors.add('Password must be at least 8 characters long'); } if (value != null && !RegExp(r'[a-z]').hasMatch(value)) { errors.add('Password must contain at least one lowercase letter'); } if (value != null && !RegExp(r'[A-Z]').hasMatch(value)) { errors.add('Password must contain at least one uppercase letter'); } if (value != null && !RegExp(r'\d').hasMatch(value)) { errors.add('Password must contain at least one number'); } if (value != null && !RegExp(r'[!"#$%&()*+,-./:;<=>?@[\]^_`{|}~]').hasMatch(value)) { errors.add('Password must contain at least one special character'); } if (errors.isNotEmpty) { return errors .join('\n'); // Join the errors with new lines to show all at once } return null; // No errors } String? fullNameValidator(String? value) { if (value == null) return 'Full name is required'; final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { return 'Full name must be between 2 and 30 characters long'; } // Test if it contains anything but alphanumeric spaces and single quote if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) { return 'Only alphanumeric characters, space, dash and single quote are allowed'; } final parts = withoutExtraSpaces.split(' '); if (parts.length < 2) return 'Full name must contain first and last names'; if (parts.length > 3) return 'Full name can at most contain 3 parts'; if (parts.any((part) => part.length < 2 || part.length > 30)) { return 'Full name parts must be between 2 and 30 characters long'; } return null; } String? reEnterPasswordCheck(String? value) { passwordValidator(value); if (signUpPassword == value) { return null; } else { return 'Passwords do not match'; } } String? reEnterPasswordCheckForgetPass(String? value) { passwordValidator(value); if (newPassword == value) { return null; } else { return 'Passwords do not match'; } } String? emailAddressValidator(String? value) { if (value != null && value.isNotEmpty && value != "") { if (checkValidityOfEmail(value)) { return null; } else { return 'Please enter a valid email'; } } else { return 'Email address is required'; } } bool checkValidityOfEmail(String? email) { if (email != null) { return RegExp( r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$") .hasMatch(email); } else { return false; } } // Function to mask the email String maskEmail(String email) { final emailParts = email.split('@'); if (emailParts.length != 2) return email; final localPart = emailParts[0]; final domainPart = emailParts[1]; if (localPart.length < 3) return email; final start = localPart.substring(0, 2); final end = localPart.substring(localPart.length - 1); final maskedLocalPart = '$start******$end'; return '$maskedLocalPart@$domainPart'; } /////////////////////////////////////API CALLS///////////////////////////////////// login() async { emit(AuthLoginLoading()); try { if (emailController.text.isEmpty || passwordController.text.isEmpty) { CustomSnackBar.displaySnackBar('Please enter your credentials'); emit(AuthLoginError(message: 'Something went wrong')); return; } token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( email: emailController.text.toLowerCase(), password: passwordController.text, ), ); } catch (failure) { emit(AuthLoginError(message: failure.toString())); return; } if (token.accessTokenIsNotEmpty) { debugPrint('token: ${token.accessToken}'); FlutterSecureStorage storage = const FlutterSecureStorage(); await storage.write( key: Token.loginAccessTokenKey, value: token.accessToken); const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); user = UserModel.fromToken(token); emailController.clear(); passwordController.clear(); emit(AuthLoginSuccess()); } else { emit(AuthLoginError(message: 'Something went wrong')); } } signUp() async { emit(AuthLoginLoading()); final response; try { List userFullName = fullName.split(' '); response = await AuthenticationAPI.signUp( model: SignUpModel( email: email.toLowerCase(), password: signUpPassword, firstName: userFullName[0], lastName: userFullName[1]), ); } catch (failure) { emit(AuthLoginError(message: failure.toString())); return; } if (response) { maskedEmail = maskEmail(email); await sendOtp(); } else { emit(AuthLoginError(message: 'Something went wrong')); } } sendOtp() async { try { emit(AuthLoading()); await AuthenticationAPI.sendOtp( body: {'email': email, 'type': 'VERIFICATION'}); emit(AuthSignUpSuccess()); } catch (_) { emit(AuthLoginError(message: 'Something went wrong')); } } Future reSendOtp() async { try { emit(AuthLoading()); await AuthenticationAPI.sendOtp( body: {'email': email, 'type': 'VERIFICATION'}); emit(ResendOtpSuccess()); return true; } catch (_) { emit(AuthLoginError(message: 'Something went wrong')); return false; } } verifyOtp(bool isForgotPass) async { emit(AuthLoginLoading()); try { final response = await AuthenticationAPI.verifyPassCode( body: {'email': email, 'type': 'VERIFICATION', 'otpCode': otpCode}); if (response['statusCode'] == 200) { if (!isForgotPass) { emailController.text = email; passwordController.text = signUpPassword; await login(); } emit(AuthOtpSuccess()); } else { emit(AuthLoginError(message: 'Something went wrong')); } } catch (failure) { emit(AuthLoginError(message: 'Something went wrong')); return; } } logout() async { emit(AuthLogoutLoading()); try { FlutterSecureStorage storage = const FlutterSecureStorage(); await storage.delete(key: Token.loginAccessTokenKey); NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil( Routes.splash, (Route route) => false, ); } catch (failure) { emit(AuthLogoutError(message: 'Something went wrong')); return; } } getTokenAndValidate() async { try { emit(AuthTokenLoading()); const storage = FlutterSecureStorage(); final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( StringsManager.firstLaunch) ?? true; if (firstLaunch) { storage.deleteAll(); } await SharedPreferencesHelper.saveBoolToSP( StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { emit(AuthTokenError(message: "Token not found")); return; } final tokenData = Token.decodeToken(value); if (tokenData.containsKey('exp')) { final exp = tokenData['exp'] ?? 0; final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; if (currentTime < exp) { emit(AuthTokenSuccess()); } else { emit(AuthTokenError(message: "Token expired")); } } else { emit(AuthTokenError(message: "Something went wrong")); } } catch (_) { emit(AuthTokenError(message: "Something went wrong")); } } sendToForgetPassword({required String password}) async { try { emit(AuthForgetPassLoading()); await AuthenticationAPI.forgetPassword(email: email, password: password); emit(AuthForgetPassSuccess()); } catch (_) { emit(AuthForgetPassError(message: 'Something went wrong')); } } }