mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-07-15 01:35:23 +00:00
350 lines
10 KiB
Dart
350 lines
10 KiB
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_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<AuthState> {
|
|
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<FormState>();
|
|
final signUpFormKey = GlobalKey<FormState>();
|
|
final checkEmailFormKey = GlobalKey<FormState>();
|
|
final createNewPasswordKey = GlobalKey<FormState>();
|
|
|
|
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<String> 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<String> 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<bool> 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'));
|
|
}
|
|
}
|
|
}
|