mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 22:57:21 +00:00
456 lines
14 KiB
Dart
456 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/api/api_exception.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;
|
|
final 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 {
|
|
final 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 APIException catch (e) {
|
|
final errorMessage = e.message;
|
|
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;
|
|
|
|
Future<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.toLowerCase(),
|
|
password: event.password,
|
|
),
|
|
);
|
|
} on APIException catch (e) {
|
|
validate = e.message;
|
|
emit(LoginInitial());
|
|
return;
|
|
} catch (e) {
|
|
validate = 'Something went wrong';
|
|
emit(LoginInitial());
|
|
return;
|
|
}
|
|
|
|
if (token.accessTokenIsNotEmpty) {
|
|
const storage = 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'));
|
|
}
|
|
}
|
|
|
|
void 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';
|
|
}
|
|
final validationErrors = <String>[];
|
|
|
|
if (!RegExp('^(?=.*[a-z])').hasMatch(value)) {
|
|
validationErrors.add(' - one lowercase letter');
|
|
}
|
|
if (!RegExp('^(?=.*[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';
|
|
}
|
|
}
|
|
|
|
Future<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 days = (time / 86400).floor(); // 86400 seconds in a day
|
|
final hours = ((time % 86400) / 3600).floor();
|
|
final minutes = (((time % 86400) % 3600) / 60).floor();
|
|
final seconds = ((time % 86400) % 3600) % 60;
|
|
|
|
final 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;
|
|
}
|
|
|
|
void changeValidate(
|
|
ChangeValidateEvent event,
|
|
Emitter<AuthState> emit,
|
|
) {
|
|
emit(AuthLoading());
|
|
validate = '';
|
|
emit(LoginInitial());
|
|
}
|
|
|
|
void 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(),
|
|
]);
|
|
}
|
|
}
|