Files
syncrow-web/lib/pages/auth/bloc/auth_bloc.dart
2025-04-21 14:23:40 +03:00

446 lines
14 KiB
Dart

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<AuthEvent, AuthState> {
AuthBloc() : super(LoginInitial()) {
on<LoginButtonPressed>(_login);
on<CheckBoxEvent>(checkBoxToggle);
on<ChangePasswordEvent>(changePassword);
on<StartTimerEvent>(_onStartTimer);
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
on<PasswordVisibleEvent>(_passwordVisible);
on<RegionInitialEvent>(_fetchRegion);
on<SelectRegionEvent>(selectRegion);
on<CheckEnableEvent>(checkEnable);
on<ChangeValidateEvent>(changeValidate);
}
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>();
final forgetRegionKey = GlobalKey<FormState>();
late bool checkValidate = false;
Timer? _timer;
int _remainingTime = 0;
List<RegionModel>? regionList = [RegionModel(name: 'name', id: 'id')];
Future _onStartTimer(StartTimerEvent event, Emitter<AuthState> 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<AuthState> emit) {
_timer?.cancel();
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> 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<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
final TextEditingController loginEmailController = TextEditingController();
final TextEditingController loginPasswordController = TextEditingController();
final loginFormKey = GlobalKey<FormState>();
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<AuthState> 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<AuthState> emit,
) {
emit(AuthLoading());
isChecked = event.newValue!;
add(CheckEnableEvent());
emit(LoginInitial());
}
// checkOtpCode(
// ChangePasswordEvent event,
// Emitter<AuthState> emit,
// ) async {
// emit(LoadingForgetState());
// await AuthenticationAPI.verifyOtp(
// email: forgetEmailController.text, otpCode: forgetOtp.text);
// emit(SuccessForgetState());
// }
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> 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<AuthState> 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<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;
}
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<String> 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<AuthState> emit) async {
try {
emit(AuthLoading());
regionList = await AuthenticationAPI.fetchRegion();
emit(AuthInitialState());
} catch (e) {
emit(LoginFailure(error: e.toString()));
}
}
Future selectRegion(SelectRegionEvent event, Emitter<AuthState> 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<AuthState> emit,
) {
emit(AuthLoading());
checkValidate = isChecked == true &&
loginPasswordController.text.isNotEmpty &&
loginEmailController.text.isNotEmpty;
emit(LoginInitial());
return checkValidate;
}
changeValidate(
ChangeValidateEvent event,
Emitter<AuthState> emit,
) {
emit(AuthLoading());
validate = '';
emit(LoginInitial());
}
changeForgetValidate(
ChangeValidateEvent event,
Emitter<AuthState> emit,
) {
emit(AuthLoading());
forgetValidate = '';
emit(LoginInitial());
}
static Future<void> logout(BuildContext context) async {
const storage = FlutterSecureStorage();
context.read<SpaceTreeBloc>().add(ClearAllData());
user = null;
context.read<HomeBloc>().user = null;
await Future.wait<void>([
ProjectManager.clearProjectUUID(),
storage.deleteAll(),
]);
}
}