import 'dart:async'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/utils/constants/strings_manager.dart'; import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AuthBloc extends Bloc { AuthBloc() : super(LoginInitial()) { on(_login); on(checkBoxToggle); on(changePassword); on(_onStartTimer); on(_onStopTimer); on(_onUpdateTimer); on(_passwordVisible); on(_fetchRegion); on(selectRegion); on(checkEnable); on(changeValidate); } ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); final forgetEmailKey = GlobalKey(); final forgetRegionKey = GlobalKey(); late bool checkValidate = false; Timer? _timer; int _remainingTime = 0; List? regionList = [RegionModel(name: 'name', id: 'id')]; Future _onStartTimer(StartTimerEvent event, Emitter emit) async { if (_validateInputs(emit)) return; if (_timer != null && _timer!.isActive) { return; } _remainingTime = 1; add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( email: forgetEmailController.text, ))!; } 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(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); } }); } void _onStopTimer(StopTimerEvent event, Emitter emit) { _timer?.cancel(); emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); } Future changePassword(ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); try { var response = await AuthenticationAPI.verifyOtp( email: forgetEmailController.text, otpCode: forgetOtp.text); if (response == true) { await AuthenticationAPI.forgetPassword( otpCode: forgetOtp.text, password: forgetPasswordController.text, email: forgetEmailController.text); _timer?.cancel(); emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(SuccessForgetState()); } } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['error']['message'] ?? 'something went wrong'; validate = errorMessage; emit(AuthInitialState()); } } String? validateCode(String? value) { if (value == null || value.isEmpty) { return 'Code is required'; } return null; } void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); } ///////////////////////////////////// login ///////////////////////////////////// final TextEditingController loginEmailController = TextEditingController(); final TextEditingController loginPasswordController = TextEditingController(); final loginFormKey = GlobalKey(); bool isChecked = true; bool obscureText = true; String newPassword = ''; String maskedEmail = ''; String otpCode = ''; String validate = ''; String forgetValidate = ''; String forgetEmailValidate = ''; // String regionUuid = ''; static Token token = Token.emptyConstructor(); static UserModel? user; bool showValidationMessage = false; void _login(LoginButtonPressed event, Emitter emit) async { emit(AuthLoading()); if (isChecked) { try { if (event.username.isEmpty || event.password.isEmpty) { CustomSnackBar.displaySnackBar('Please enter your credentials'); emit(const LoginFailure(error: 'Something went wrong')); return; } token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( email: event.username, password: event.password, ), ); } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['error']['message']; if (errorMessage == "Access denied for web platform") { validate = errorMessage; } else { validate = 'Invalid Credentials!'; } emit(LoginInitial()); return; } if (token.accessTokenIsNotEmpty) { 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); loginEmailController.clear(); loginPasswordController.clear(); emit(LoginSuccess()); } else { emit(const LoginFailure(error: 'Something went wrong')); } } else { emit(const LoginFailure(error: 'Accept terms and condition')); } } checkBoxToggle( CheckBoxEvent event, Emitter emit, ) { emit(AuthLoading()); isChecked = event.newValue!; add(CheckEnableEvent()); emit(LoginInitial()); } // checkOtpCode( // ChangePasswordEvent event, // Emitter emit, // ) async { // emit(LoadingForgetState()); // await AuthenticationAPI.verifyOtp( // email: forgetEmailController.text, otpCode: forgetOtp.text); // emit(SuccessForgetState()); // } void _passwordVisible(PasswordVisibleEvent event, Emitter emit) { emit(AuthLoading()); obscureText = !event.newValue!; emit(PasswordVisibleState()); } void launchURL(String url) {} /////////////////////////////////////VALIDATORS///////////////////////////////////// String? validatePassword(String? value) { if (value == null || value.isEmpty) { return ''; } return null; } String? validateEmail(String? value) { if (value == null || value.isEmpty) { return 'Email is required'; } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { return 'Enter a valid email address'; } // else if (regionUuid == '') { // return 'Please select your region'; // } validate = ''; return null; } String? loginValidateEmail(String? value) { if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { return ''; } return null; } bool _validateInputs(Emitter emit) { emit(LoadingForgetState()); final nameError = validateEmail(forgetEmailController.text); if (nameError != null) { forgetEmailValidate = nameError; emit(AuthInitialState()); // emit(FailureForgetState(error: nameError)); return true; } return false; } String? validateRegion(String? value) { if (value == null || value.isEmpty) { return 'Please select a region'; } return null; } 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; } 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'; } 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 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'; } static Future getTokenAndValidate() async { try { 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) { return 'Token not found'; } final tokenData = Token.decodeToken(value); if (tokenData.containsKey('exp')) { final exp = tokenData['exp'] ?? 0; final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; if (currentTime < exp) { return 'Success'; } else { return 'expired'; } } else { return 'Something went wrong'; } } catch (_) { return 'Something went wrong'; } } void _fetchRegion(RegionInitialEvent event, Emitter emit) async { try { emit(AuthLoading()); regionList = await AuthenticationAPI.fetchRegion(); emit(AuthInitialState()); } catch (e) { emit(LoginFailure(error: e.toString())); } } Future selectRegion(SelectRegionEvent event, Emitter emit) async { try { emit(AuthLoading()); // regionUuid = event.val; add(CheckEnableEvent()); emit(AuthInitialState()); } catch (e) { emit(FailureForgetState(error: e.toString())); return; } } 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; } bool checkEnable( CheckEnableEvent event, Emitter emit, ) { emit(AuthLoading()); checkValidate = isChecked == true && loginPasswordController.text.isNotEmpty && loginEmailController.text.isNotEmpty; emit(LoginInitial()); return checkValidate; } changeValidate( ChangeValidateEvent event, Emitter emit, ) { emit(AuthLoading()); validate = ''; emit(LoginInitial()); } changeForgetValidate( ChangeValidateEvent event, Emitter emit, ) { emit(AuthLoading()); forgetValidate = ''; emit(LoginInitial()); } static Future logout(BuildContext context) async { const storage = FlutterSecureStorage(); context.read().add(ClearAllData()); user = null; context.read().user = null; await Future.wait([ ProjectManager.clearProjectUUID(), storage.deleteAll(), ]); } }