mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-07-17 02:25:16 +00:00
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:
@ -1,4 +1,3 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@ -42,6 +41,14 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_instance = null;
|
||||
selectedSpace = null;
|
||||
selectedRoom = null;
|
||||
return super.close();
|
||||
}
|
||||
|
||||
static HomeCubit get(context) => BlocProvider.of(context);
|
||||
|
||||
List<SpaceModel>? spaces;
|
||||
@ -124,8 +131,8 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
selectedSpace = spaces!.first
|
||||
: null;
|
||||
emitSafe(GetSpacesLoaded(spaces!));
|
||||
} on DioException catch (e) {
|
||||
emitSafe(GetSpacesError(ServerFailure.fromDioError(e).errMessage));
|
||||
} on ServerFailure catch (failure) {
|
||||
emitSafe(GetSpacesError(failure.errMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,17 +145,13 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
} else {
|
||||
emitSafe(GetSpaceRoomsError("No rooms found"));
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
emitSafe(GetSpacesError(ServerFailure.fromDioError(e).errMessage));
|
||||
} on ServerFailure catch (failure) {
|
||||
emitSafe(GetSpacesError(failure.errMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////// Nav ///////////////////////////////////////
|
||||
|
||||
static clear() {
|
||||
pageIndex = 0;
|
||||
}
|
||||
|
||||
static int pageIndex = 0;
|
||||
|
||||
static Map<String, List<Widget>> appBarActions = {
|
||||
|
@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/app_layout/view/widgets/app_body.dart';
|
||||
import 'package:syncrow_app/features/app_layout/view/widgets/default_app_bar.dart';
|
||||
import 'package:syncrow_app/features/app_layout/view/widgets/default_nav_bar.dart';
|
||||
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
|
||||
import 'package:syncrow_app/features/auth/model/token.dart';
|
||||
import 'package:syncrow_app/navigation/routing_constants.dart';
|
||||
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
||||
|
||||
@ -14,53 +17,74 @@ class AppLayout extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => HomeCubit.getInstance(),
|
||||
child: BlocConsumer<HomeCubit, HomeState>(
|
||||
listener: (context, state) {
|
||||
if (state is GetSpacesError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errMessage),
|
||||
),
|
||||
);
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.settings.name == Routes.authLogin);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return AnnotatedRegion(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: ColorsManager.backgroundColor,
|
||||
extendBodyBehindAppBar: true,
|
||||
extendBody: true,
|
||||
appBar: HomeCubit.getInstance().spaces != null
|
||||
? const DefaultAppBar()
|
||||
: null,
|
||||
body: const AppBody(),
|
||||
bottomNavigationBar: const DefaultNavBar(),
|
||||
// floatingActionButton: FloatingActionButton(
|
||||
// onPressed: () {
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// CustomPageRoute(
|
||||
// builder: (context) =>
|
||||
// const ThreeGangSwitchesView(),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: const Icon(Icons.arrow_forward_ios_sharp),
|
||||
// ),
|
||||
),
|
||||
return BlocConsumer<AuthCubit, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.settings.name == Routes.authLogin);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return BlocProvider(
|
||||
create: (context) => HomeCubit.getInstance(),
|
||||
child: BlocConsumer<HomeCubit, HomeState>(
|
||||
listener: (context, state) async {
|
||||
if (state is GetSpacesError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errMessage),
|
||||
),
|
||||
);
|
||||
Navigator.of(context).popUntil(
|
||||
(route) => route.settings.name == Routes.authLogin);
|
||||
}
|
||||
String? token = await const FlutterSecureStorage()
|
||||
.read(key: Token.loginAccessTokenKey);
|
||||
if (token == null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).popAndPushNamed(Routes.authLogin);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return AnnotatedRegion(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: ColorsManager.backgroundColor,
|
||||
extendBodyBehindAppBar: true,
|
||||
extendBody: true,
|
||||
appBar: HomeCubit.getInstance().spaces != null
|
||||
? const DefaultAppBar()
|
||||
: null,
|
||||
body: const AppBody(),
|
||||
bottomNavigationBar: const DefaultNavBar(),
|
||||
// floatingActionButton: FloatingActionButton(
|
||||
// onPressed: () {
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// CustomPageRoute(
|
||||
// builder: (context) =>
|
||||
// const ThreeGangSwitchesView(),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: const Icon(Icons.arrow_forward_ios_sharp),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -31,12 +31,9 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
bool _isClosed = false;
|
||||
|
||||
static DevicesCubit? _instance;
|
||||
static DevicesCubit getInstance() {
|
||||
print('device cubit instance found : ${_instance != null}');
|
||||
print('selected space : ${HomeCubit.getInstance().selectedSpace != null}');
|
||||
return _instance ??= DevicesCubit._();
|
||||
}
|
||||
|
||||
@ -44,7 +41,6 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_isClosed = true;
|
||||
_instance = null;
|
||||
return super.close();
|
||||
}
|
||||
@ -272,56 +268,37 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
emitSafe(DeviceControlError('Failed to control the device'));
|
||||
}
|
||||
});
|
||||
} on DioException catch (e) {
|
||||
emitSafe(DeviceControlError(ServerFailure.fromDioError(e).errMessage));
|
||||
} on ServerFailure catch (failure) {
|
||||
emitSafe(DeviceControlError(failure.errMessage));
|
||||
}
|
||||
}
|
||||
|
||||
fetchGroups(int spaceId) async {
|
||||
if (_isClosed) return;
|
||||
|
||||
try {
|
||||
emitSafe(DevicesCategoriesLoading());
|
||||
allCategories = await DevicesAPI.fetchGroups(spaceId);
|
||||
emitSafe(DevicesCategoriesSuccess());
|
||||
} on DioException catch (error) {
|
||||
emitSafe(
|
||||
DevicesCategoriesError(ServerFailure.fromDioError(error).errMessage),
|
||||
);
|
||||
}
|
||||
emitSafe(DevicesCategoriesLoading());
|
||||
allCategories = await DevicesAPI.fetchGroups(spaceId);
|
||||
emitSafe(DevicesCategoriesSuccess());
|
||||
}
|
||||
|
||||
fetchDevicesByRoomId(int? roomId) async {
|
||||
if (_isClosed) return;
|
||||
if (roomId == null) return;
|
||||
|
||||
try {
|
||||
emitSafe(GetDevicesLoading());
|
||||
int roomIndex = HomeCubit.getInstance()
|
||||
.selectedSpace!
|
||||
.rooms!
|
||||
.indexWhere((element) => element.id == roomId);
|
||||
HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices =
|
||||
await SpacesAPI.getDevicesByRoomId(roomId);
|
||||
//get status for each device
|
||||
for (var device in HomeCubit.getInstance()
|
||||
.selectedSpace!
|
||||
.rooms![roomIndex]
|
||||
.devices!) {
|
||||
getDevicesStatues(device.id!, roomIndex);
|
||||
}
|
||||
|
||||
emitSafe(GetDevicesSuccess());
|
||||
} on DioException catch (error) {
|
||||
emitSafe(
|
||||
GetDevicesError(ServerFailure.fromDioError(error).errMessage),
|
||||
);
|
||||
emitSafe(GetDevicesLoading());
|
||||
int roomIndex = HomeCubit.getInstance()
|
||||
.selectedSpace!
|
||||
.rooms!
|
||||
.indexWhere((element) => element.id == roomId);
|
||||
HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices =
|
||||
await SpacesAPI.getDevicesByRoomId(roomId);
|
||||
//get status for each device
|
||||
for (var device
|
||||
in HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices!) {
|
||||
getDevicesStatues(device.id!, roomIndex);
|
||||
}
|
||||
|
||||
emitSafe(GetDevicesSuccess());
|
||||
}
|
||||
|
||||
getDevicesStatues(String deviceId, int roomIndex, {String? code}) async {
|
||||
if (_isClosed) return;
|
||||
|
||||
try {
|
||||
emitSafe(GetDeviceStatusLoading(code: code));
|
||||
int deviceIndex = HomeCubit.getInstance()
|
||||
@ -345,9 +322,9 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
.devices![deviceIndex]
|
||||
.status = statuses;
|
||||
emitSafe(GetDeviceStatusSuccess(code: code));
|
||||
} on DioException catch (error) {
|
||||
} on ServerFailure catch (failure) {
|
||||
emitSafe(
|
||||
GetDeviceStatusError(ServerFailure.fromDioError(error).errMessage),
|
||||
GetDeviceStatusError(failure.errMessage),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user