Refactor HTTPInterceptor and add CustomSnackBar helper

Refactor HTTPInterceptor to handle error responses and add a CustomSnackBar
helper to display snack bars. This will improve error handling and user
feedback in the application.
This commit is contained in:
Mohammad Salameh
2024-04-15 12:02:34 +03:00
parent 590c70a7d8
commit cfc395e210
10 changed files with 287 additions and 188 deletions

View File

@ -1,13 +1,13 @@
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.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/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/auth/model/login_with_email_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/services/api/network_exception.dart';
@ -43,7 +43,7 @@ class AuthCubit extends Cubit<AuthState> {
static Token token = Token.emptyConstructor();
login() async {
emit(AuthLoading());
emit(AuthLoginLoading());
try {
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
@ -60,24 +60,27 @@ class AuthCubit extends Cubit<AuthState> {
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
emit(AuthSuccess());
emailController.clear();
passwordController.clear();
emit(AuthLoginSuccess());
} else {
emit(AuthError('Something went wrong'));
emit(AuthLoginError(message: 'Something went wrong'));
}
} on DioException catch (e) {
emit(AuthError(ServerFailure.fromDioError(e).toString()));
} on ServerFailure catch (failure) {
emit(AuthError(message: failure.errMessage));
}
}
logout() async {
emit(AuthLoading());
emit(AuthLogoutLoading());
try {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.delete(key: Token.loginAccessTokenKey);
HomeCubit.clear();
emit(AuthLoggedOut());
} on DioException catch (e) {
emit(AuthError(ServerFailure.fromDioError(e).errMessage));
NavigationService.navigatorKey.currentState!
.popAndPushNamed(Routes.authLogin);
emit(AuthLogoutSuccess());
} on ServerFailure catch (failure) {
emit(AuthError(message: failure.errMessage));
}
}
@ -87,7 +90,7 @@ class AuthCubit extends Cubit<AuthState> {
await const FlutterSecureStorage().read(key: Token.loginAccessTokenKey);
if (value == null) {
emit(AuthTokenError("Token not found"));
emit(AuthTokenError(message: "Token not found"));
return;
}
@ -100,10 +103,17 @@ class AuthCubit extends Cubit<AuthState> {
if (currentTime < exp) {
emit(AuthTokenSuccess());
} else {
emit(AuthTokenError("Token expired"));
emit(AuthTokenError(message: "Token expired"));
}
} else {
emit(AuthTokenError("Something went wrong"));
emit(AuthTokenError(message: "Something went wrong"));
}
}
static void logUserOut() async {
user = null;
token = Token.emptyConstructor();
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.delete(key: Token.loginAccessTokenKey);
}
}

View File

@ -32,21 +32,25 @@ class LoginForm extends StatelessWidget {
validator: (value) {
if (state is! AuthTokenError) {
if (value != null) {
if (value.isEmpty) {
if (value.isNotEmpty) {
if (RegExp(
r'^[a-zA-Z0-9._-]+@{1}[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$')
.hasMatch(value)) {
return null;
} else {
return 'Please enter a valid email';
}
} else {
return 'Please enter your email';
}
//Regex for email validation
if (!RegExp(
r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$')
.hasMatch(value)) {
return 'Please enter a valid email';
}
} else {
return 'Please enter your email';
}
}
return null;
},
onTapOutside: (event) {
formKey.currentState!.validate();
FocusScope.of(context).unfocus();
},
decoration: defaultInputDecoration(context,
hint: "Example@email.com"),
@ -62,9 +66,19 @@ class LoginForm extends StatelessWidget {
if (state is! AuthTokenError) {
if (value != null) {
if (value.isNotEmpty) {
if (value.length < 6) {
return 'Password must be at least 8 characters';
}
return null;
//TODO: uncomment this when the backend is ready
// if (value.length > 8) {
// if (RegExp(
// r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$')
// .hasMatch(value)) {
// return null;
// } else {
// return 'Password must contain at least one uppercase letter, one lowercase letter, one number and one special character';
// }
// } else {
// return 'Password must be at least 8 characters';
// }
} else {
return 'Please enter your password';
}
@ -73,7 +87,7 @@ class LoginForm extends StatelessWidget {
return null;
},
onTapOutside: (event) {
formKey.currentState!.validate();
FocusScope.of(context).unfocus();
},
obscureText: !AuthCubit.get(context).isPasswordVisible,
decoration: defaultInputDecoration(context,
@ -88,8 +102,16 @@ class LoginForm extends StatelessWidget {
children: [
Expanded(
child: DefaultButton(
isDone: state is AuthSuccess,
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
// enabled: AuthCubit.get(context)
// .emailController
// .text
// .isNotEmpty &&
// AuthCubit.get(context)
// .passwordController
// .text
// .isNotEmpty,
customButtonStyle: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Colors.black.withOpacity(.25),
@ -103,8 +125,10 @@ class LoginForm extends StatelessWidget {
),
onPressed: () {
if (formKey.currentState!.validate()) {
AuthCubit.get(context).login();
FocusScope.of(context).unfocus();
if (state is! AuthLoginLoading) {
AuthCubit.get(context).login();
FocusScope.of(context).unfocus();
}
}
},
),