diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart index 8fee3a4..7ea5011 100644 --- a/lib/features/app_layout/bloc/home_cubit.dart +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -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 { } } + @override + Future close() { + _instance = null; + selectedSpace = null; + selectedRoom = null; + return super.close(); + } + static HomeCubit get(context) => BlocProvider.of(context); List? spaces; @@ -118,14 +125,17 @@ class HomeCubit extends Cubit { emitSafe(GetSpacesLoading()); try { spaces = await SpacesAPI.getSpaces(); - selectedSpace = spaces!.isNotEmpty - ? - // selectSpace(spaces!.first) - selectedSpace = spaces!.first - : null; - emitSafe(GetSpacesLoaded(spaces!)); - } on DioException catch (e) { - emitSafe(GetSpacesError(ServerFailure.fromDioError(e).errMessage)); + } catch (failure) { + emitSafe(GetSpacesError(failure.toString())); + return; + } + + if (spaces != null && spaces!.isNotEmpty) { + selectedSpace = spaces!.first; + emitSafe(GetSpacesSuccess(spaces!)); + fetchRooms(selectedSpace!); + } else { + emitSafe(GetSpacesError("No spaces found")); } } @@ -133,22 +143,19 @@ class HomeCubit extends Cubit { emitSafe(GetSpaceRoomsLoading()); try { space.rooms = await SpacesAPI.getRoomsBySpaceId(space.id!); - if (space.rooms != null) { - emitSafe(GetSpaceRoomsLoaded(space.rooms!)); - } else { - emitSafe(GetSpaceRoomsError("No rooms found")); - } - } on DioException catch (e) { - emitSafe(GetSpacesError(ServerFailure.fromDioError(e).errMessage)); + } catch (failure) { + emitSafe(GetSpaceRoomsError(failure.toString())); + return; + } + if (space.rooms != null) { + emitSafe(GetSpaceRoomsSuccess(space.rooms!)); + } else { + emitSafe(GetSpaceRoomsError("No rooms found")); } } /////////////////////////////////////// Nav /////////////////////////////////////// - static clear() { - pageIndex = 0; - } - static int pageIndex = 0; static Map> appBarActions = { @@ -166,14 +173,6 @@ class HomeCubit extends Cubit { ), ], 'Devices': [ - // IconButton( - // icon: Image.asset( - // Assets.iconsFilter, - // height: 20, - // width: 20, - // ), - // onPressed: () {}, - // ), IconButton( icon: const Icon( Icons.add, diff --git a/lib/features/app_layout/bloc/home_state.dart b/lib/features/app_layout/bloc/home_state.dart index d1f5b7d..9c20d4e 100644 --- a/lib/features/app_layout/bloc/home_state.dart +++ b/lib/features/app_layout/bloc/home_state.dart @@ -4,33 +4,45 @@ abstract class HomeState {} class HomeInitial extends HomeState {} -class GetSpacesLoading extends HomeState {} +//base states +class HomeLoading extends HomeState {} -class GetSpacesLoaded extends HomeState { +class HomeError extends HomeState { + final String errMessage; + + HomeError(this.errMessage); +} + +class HomeSuccess extends HomeState {} + +///specific states +//get spaces +class GetSpacesLoading extends HomeLoading {} + +class GetSpacesSuccess extends HomeSuccess { final List spaces; - GetSpacesLoaded(this.spaces); + GetSpacesSuccess(this.spaces); } -class GetSpacesError extends HomeState { - final String errMessage; - - GetSpacesError(this.errMessage); +class GetSpacesError extends HomeError { + GetSpacesError(super.errMessage); } -class GetSpaceRoomsLoading extends HomeState {} +//get rooms +class GetSpaceRoomsLoading extends HomeLoading {} -class GetSpaceRoomsLoaded extends HomeState { +class GetSpaceRoomsSuccess extends HomeSuccess { final List rooms; - GetSpaceRoomsLoaded(this.rooms); + GetSpaceRoomsSuccess(this.rooms); } -class GetSpaceRoomsError extends HomeState { - final String errMessage; - GetSpaceRoomsError(this.errMessage); +class GetSpaceRoomsError extends HomeError { + GetSpaceRoomsError(super.errMessage); } +//UI states class SpaceSelected extends HomeState { final SpaceModel space; diff --git a/lib/features/app_layout/view/app_layout.dart b/lib/features/app_layout/view/app_layout.dart index 6fbdb42..45a789c 100644 --- a/lib/features/app_layout/view/app_layout.dart +++ b/lib/features/app_layout/view/app_layout.dart @@ -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( - 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( + 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( + 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), + // ), + ), + ), + ); + }, + ), + ); + }, ); } } diff --git a/lib/features/app_layout/view/widgets/app_body.dart b/lib/features/app_layout/view/widgets/app_body.dart index 8e3fa72..6e80454 100644 --- a/lib/features/app_layout/view/widgets/app_body.dart +++ b/lib/features/app_layout/view/widgets/app_body.dart @@ -12,7 +12,16 @@ class AppBody extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listener: (context, state) { + if (state is GetSpacesError) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.errMessage), + // ), + // ); + } + }, builder: (context, state) { return Container( width: MediaQuery.sizeOf(context).width, @@ -26,24 +35,11 @@ class AppBody extends StatelessWidget { opacity: 0.4, ), ), - child: BlocConsumer( - listener: (context, state) { - if (state is GetSpacesError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errMessage), - ), - ); - } - }, - builder: (context, state) { - return state is! GetSpacesLoading - ? state is! GetSpaceRoomsLoading - ? HomeCubit.getInstance().pages[HomeCubit.pageIndex] - : const Center(child: CircularProgressIndicator()) - : const Center(child: CircularProgressIndicator()); - }, - ), + child: state is! GetSpacesLoading + ? state is! GetSpaceRoomsLoading + ? HomeCubit.getInstance().pages[HomeCubit.pageIndex] + : const Center(child: CircularProgressIndicator()) + : const Center(child: CircularProgressIndicator()), ); }, ); diff --git a/lib/features/app_layout/view/widgets/default_nav_bar.dart b/lib/features/app_layout/view/widgets/default_nav_bar.dart index c5140b1..178faa5 100644 --- a/lib/features/app_layout/view/widgets/default_nav_bar.dart +++ b/lib/features/app_layout/view/widgets/default_nav_bar.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; -import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; -import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index d966e90..0c2fcdf 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -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'; @@ -20,8 +20,9 @@ class AuthCubit extends Cubit { static AuthCubit get(context) => BlocProvider.of(context); - TextEditingController emailController = TextEditingController(); - TextEditingController passwordController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final loginFormKey = GlobalKey(); bool isPasswordVisible = false; static GlobalKey formKey = GlobalKey(); @@ -42,8 +43,58 @@ class AuthCubit extends Cubit { static Token token = Token.emptyConstructor(); +/////////////////////////////////////VALIDATORS///////////////////////////////////// + String? passwordValidator(String? value) { + if (value != null) { + if (value.isNotEmpty) { + if (value.length >= 6) { + return null; + //TODO uncomment this code when the password validation is needed + // 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 6 characters'; + } + } else { + return 'Please enter your password'; + } + } + return null; + } + + String? emailAddressValidator(String? value) { + if (value != null && value.isNotEmpty && value != "") { + if (checkValidityOfEmail(value)) { + if (loginFormKey.currentState != null) { + loginFormKey.currentState!.save(); + } + 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; + } + } + +/////////////////////////////////////API CALLS///////////////////////////////////// login() async { - emit(AuthLoading()); + emit(AuthLoginLoading()); try { token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( @@ -51,33 +102,37 @@ class AuthCubit extends Cubit { password: passwordController.text, ), ); - - 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); - emit(AuthSuccess()); - } else { - emit(AuthError('Something went wrong')); - } - } on DioException catch (e) { - emit(AuthError(ServerFailure.fromDioError(e).toString())); + } catch (failure) { + emit(AuthLoginError(message: failure.toString())); + 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); + emailController.clear(); + passwordController.clear(); + emit(AuthLoginSuccess()); + } else { + emit(AuthLoginError(message: 'Something went wrong')); } } 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()); + } catch (failure) { + emit(AuthLogoutError(message: failure.toString())); + return; } } @@ -87,7 +142,7 @@ class AuthCubit extends Cubit { await const FlutterSecureStorage().read(key: Token.loginAccessTokenKey); if (value == null) { - emit(AuthTokenError("Token not found")); + emit(AuthTokenError(message: "Token not found")); return; } @@ -100,10 +155,10 @@ class AuthCubit extends Cubit { 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")); } } } diff --git a/lib/features/auth/bloc/auth_state.dart b/lib/features/auth/bloc/auth_state.dart index e84791f..38c7180 100644 --- a/lib/features/auth/bloc/auth_state.dart +++ b/lib/features/auth/bloc/auth_state.dart @@ -4,30 +4,44 @@ abstract class AuthState {} class AuthInitial extends AuthState {} +//base states class AuthLoading extends AuthState {} class AuthError extends AuthState { final String message; - - AuthError(this.message) { - debugPrint(message); - } + String? code; + AuthError({required this.message, this.code}); } class AuthSuccess extends AuthState {} -class AuthLoggedOut extends AuthState {} +//user log states +class AuthLoginLoading extends AuthLoading {} +class AuthLoginSuccess extends AuthSuccess {} + +class AuthLoginError extends AuthError { + AuthLoginError({required super.message, super.code}); +} + +class AuthLogoutLoading extends AuthLoading {} + +class AuthLogoutSuccess extends AuthSuccess {} + +class AuthLogoutError extends AuthError { + AuthLogoutError({required super.message, super.code}); +} + +// UI states class AuthPasswordVisibilityChanged extends AuthState {} class AuthAgreeToTermsChanged extends AuthState {} -class AuthTokenLoading extends AuthState {} +//token states +class AuthTokenLoading extends AuthLoading {} -class AuthTokenSuccess extends AuthState {} +class AuthTokenSuccess extends AuthSuccess {} -class AuthTokenError extends AuthState { - final String message; - - AuthTokenError(this.message); +class AuthTokenError extends AuthError { + AuthTokenError({required super.message, super.code}); } diff --git a/lib/features/auth/view/widgets/login/login_form.dart b/lib/features/auth/view/widgets/login/login_form.dart index ed76b7b..5296e23 100644 --- a/lib/features/auth/view/widgets/login/login_form.dart +++ b/lib/features/auth/view/widgets/login/login_form.dart @@ -13,9 +13,10 @@ class LoginForm extends StatelessWidget { @override Widget build(BuildContext context) { - var formKey = GlobalKey(); + var pressed = false; return BlocBuilder( builder: (context, state) { + final formKey = AuthCubit.get(context).loginFormKey; return Form( key: formKey, autovalidateMode: AutovalidateMode.onUserInteraction, @@ -28,26 +29,23 @@ class LoginForm extends StatelessWidget { fontColor: Colors.white, ), TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.email], controller: AuthCubit.get(context).emailController, validator: (value) { - if (state is! AuthTokenError) { - if (value != null) { - if (value.isEmpty) { - 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'; - } - } + if (state is AuthTokenError && !pressed) { + return null; } - return null; + return AuthCubit.get(context).emailAddressValidator(value); }, onTapOutside: (event) { - formKey.currentState!.validate(); + FocusScope.of(context).unfocus(); }, + onChanged: (value) {}, decoration: defaultInputDecoration(context, hint: "Example@email.com"), ), @@ -57,23 +55,21 @@ class LoginForm extends StatelessWidget { fontColor: Colors.white, ), TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.password], controller: AuthCubit.get(context).passwordController, validator: (value) { - if (state is! AuthTokenError) { - if (value != null) { - if (value.isNotEmpty) { - if (value.length < 6) { - return 'Password must be at least 8 characters'; - } - } else { - return 'Please enter your password'; - } - } + if (state is AuthTokenError && !pressed) { + return null; } - return null; + return AuthCubit.get(context).passwordValidator(value); }, onTapOutside: (event) { - formKey.currentState!.validate(); + FocusScope.of(context).unfocus(); }, obscureText: !AuthCubit.get(context).isPasswordVisible, decoration: defaultInputDecoration(context, @@ -88,7 +84,7 @@ class LoginForm extends StatelessWidget { children: [ Expanded( child: DefaultButton( - isDone: state is AuthSuccess, + isDone: state is AuthLoginSuccess, isLoading: state is AuthLoading, customButtonStyle: ButtonStyle( backgroundColor: MaterialStateProperty.all( @@ -102,9 +98,12 @@ class LoginForm extends StatelessWidget { 'Login', ), onPressed: () { + pressed = true; if (formKey.currentState!.validate()) { - AuthCubit.get(context).login(); - FocusScope.of(context).unfocus(); + if ((state is! AuthLoading)) { + AuthCubit.get(context).login(); + FocusScope.of(context).unfocus(); + } } }, ), diff --git a/lib/features/auth/view/widgets/login/login_view.dart b/lib/features/auth/view/widgets/login/login_view.dart index 06713c2..29e7920 100644 --- a/lib/features/auth/view/widgets/login/login_view.dart +++ b/lib/features/auth/view/widgets/login/login_view.dart @@ -18,18 +18,16 @@ class LoginView extends StatelessWidget { @override Widget build(BuildContext context) { - //TODO move to strings manager - return BlocConsumer( listener: (context, state) { if (state is AuthError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - ), - ); - } else if (state is AuthSuccess) { - Navigator.popAndPushNamed(context, Routes.homeRoute); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.message), + // ), + // ); + } else if (state is AuthLoginSuccess) { + Navigator.pushNamed(context, Routes.homeRoute); } }, builder: (context, state) { diff --git a/lib/features/devices/bloc/devices_cubit.dart b/lib/features/devices/bloc/devices_cubit.dart index 7b17513..5784091 100644 --- a/lib/features/devices/bloc/devices_cubit.dart +++ b/lib/features/devices/bloc/devices_cubit.dart @@ -31,12 +31,9 @@ class DevicesCubit extends Cubit { } } } - 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 { @override Future close() { - _isClosed = true; _instance = null; return super.close(); } @@ -255,101 +251,92 @@ class DevicesCubit extends Cubit { code: control.code, )); try { - await DevicesAPI.controlDevice(control).then((response) { - emitSafe(DeviceControlSuccess( - code: control.code, - )); - if (response['success'] ?? false) { - Future.delayed(const Duration(milliseconds: 400), () { - getDevicesStatues( - deviceId, - HomeCubit.getInstance().selectedSpace!.rooms!.indexOf( - HomeCubit.getInstance().selectedRoom!, - ), - code: control.code); - }); - } else { - emitSafe(DeviceControlError('Failed to control the device')); - } - }); - } on DioException catch (e) { - emitSafe(DeviceControlError(ServerFailure.fromDioError(e).errMessage)); + var response = await DevicesAPI.controlDevice(control); + + if (response['success'] ?? false) { + emitSafe(DeviceControlSuccess(code: control.code)); + //this delay is to give tuya server time to update the status + Future.delayed(const Duration(milliseconds: 400), () { + getDevicesStatues( + deviceId, + HomeCubit.getInstance() + .selectedSpace! + .rooms! + .indexOf(HomeCubit.getInstance().selectedRoom!), + code: control.code); + }); + } else { + emitSafe(DeviceControlError('Failed to control the device')); + } + } catch (failure) { + emitSafe(DeviceControlError(failure.toString())); + return; } } fetchGroups(int spaceId) async { - if (_isClosed) return; - + emitSafe(DevicesCategoriesLoading()); try { - emitSafe(DevicesCategoriesLoading()); allCategories = await DevicesAPI.fetchGroups(spaceId); + } catch (e) { + emitSafe(DevicesCategoriesError(e.toString())); + return; + } + if (allCategories!.isNotEmpty && allCategories != null) { emitSafe(DevicesCategoriesSuccess()); - } on DioException catch (error) { - emitSafe( - DevicesCategoriesError(ServerFailure.fromDioError(error).errMessage), - ); + } else { + emitSafe(DevicesCategoriesError('No Groups found')); } } fetchDevicesByRoomId(int? roomId) async { - if (_isClosed) return; if (roomId == null) return; + emitSafe(GetDevicesLoading()); + int roomIndex = HomeCubit.getInstance() + .selectedSpace! + .rooms! + .indexWhere((element) => element.id == roomId); 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); - } + await DevicesAPI.getDevicesByRoomId(roomId); + } catch (e) { + emitSafe(GetDevicesError(e.toString())); + return; + } + emitSafe(GetDevicesSuccess()); - emitSafe(GetDevicesSuccess()); - } on DioException catch (error) { - emitSafe( - GetDevicesError(ServerFailure.fromDioError(error).errMessage), - ); + //get status for each device + //TODO get devices status per page via page controller instead of getting all devices status at once + for (var device + in HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices!) { + getDevicesStatues(device.id!, roomIndex); } } getDevicesStatues(String deviceId, int roomIndex, {String? code}) async { - if (_isClosed) return; - + emitSafe(GetDeviceStatusLoading(code: code)); + int deviceIndex = HomeCubit.getInstance() + .selectedSpace! + .rooms![roomIndex] + .devices! + .indexWhere((element) => element.id == deviceId); + List statuses = []; try { - emitSafe(GetDeviceStatusLoading(code: code)); - int deviceIndex = HomeCubit.getInstance() - .selectedSpace! - .rooms![roomIndex] - .devices! - .indexWhere((element) => element.id == deviceId); - - List statuses = []; var response = await DevicesAPI.getDeviceStatus(deviceId); - // if (response['result']['status'].length > 4) - // print('response : ${response['result']['status'][4]}'); - for (var status in response['result']['status']) { statuses.add(StatusModel.fromJson(status)); } - - HomeCubit.getInstance() - .selectedSpace! - .rooms![roomIndex] - .devices![deviceIndex] - .status = statuses; - emitSafe(GetDeviceStatusSuccess(code: code)); - } on DioException catch (error) { - emitSafe( - GetDeviceStatusError(ServerFailure.fromDioError(error).errMessage), - ); + } catch (e) { + emitSafe(GetDeviceStatusError(e.toString())); + return; } + HomeCubit.getInstance() + .selectedSpace! + .rooms![roomIndex] + .devices![deviceIndex] + .status = statuses; + emitSafe(GetDeviceStatusSuccess(code: code)); } ///Lights diff --git a/lib/features/devices/view/widgets/ACs/ac_interface.dart b/lib/features/devices/view/widgets/ACs/ac_interface.dart index 23c45f7..95a164a 100644 --- a/lib/features/devices/view/widgets/ACs/ac_interface.dart +++ b/lib/features/devices/view/widgets/ACs/ac_interface.dart @@ -27,11 +27,11 @@ class AcInterface extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is DeviceControlError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errorMsg), - ), - ); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.errorMsg), + // ), + // ); } }, builder: (context, state) { diff --git a/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart b/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart index dab1d71..f417936 100644 --- a/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart +++ b/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart @@ -11,7 +11,6 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dar import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; -import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; class AcInterfaceTempUnit extends StatelessWidget { diff --git a/lib/features/devices/view/widgets/devices_view_body.dart b/lib/features/devices/view/widgets/devices_view_body.dart index 6edb972..7a9d8fe 100644 --- a/lib/features/devices/view/widgets/devices_view_body.dart +++ b/lib/features/devices/view/widgets/devices_view_body.dart @@ -19,25 +19,25 @@ class DevicesViewBody extends StatelessWidget { Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { - if (state is GetDevicesError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errorMsg), - ), - ); - } else if (state is DevicesCategoriesError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errorMsg), - ), - ); - } else if (state is DeviceControlError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.errorMsg), - ), - ); - } + // if (state is GetDevicesError) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.errorMsg), + // ), + // ); + // } else if (state is DevicesCategoriesError) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.errorMsg), + // ), + // ); + // } else if (state is DeviceControlError) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.errorMsg), + // ), + // ); + // } }, builder: (context, state) { if (state is DevicesLoading || diff --git a/lib/features/devices/view/widgets/smart_door/door_interface.dart b/lib/features/devices/view/widgets/smart_door/door_interface.dart new file mode 100644 index 0000000..cf2a2ff --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_interface.dart @@ -0,0 +1,92 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; +// import 'package:syncrow_app/features/devices/model/device_model.dart'; +// import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +// import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +// import 'package:syncrow_app/generated/assets.dart'; +// import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +// import 'package:syncrow_app/utils/resource_manager/constants.dart'; +// import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +// class DoorInterface extends StatelessWidget { +// const DoorInterface({super.key, required this.doorlock}); + +// final DeviceModel doorlock; + +// @override +// Widget build(BuildContext context) { +// 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: AppBar( +// backgroundColor: Colors.transparent, +// centerTitle: true, +// title: BodyLarge( +// text: doorlock.name ?? "", +// fontColor: ColorsManager.primaryColor, +// fontWeight: FontsManager.bold, +// ), +// ), +// body: Container( +// width: MediaQuery.sizeOf(context).width, +// height: MediaQuery.sizeOf(context).height, +// decoration: const BoxDecoration( +// image: DecorationImage( +// image: AssetImage( +// Assets.imagesBackground, +// ), +// fit: BoxFit.cover, +// opacity: 0.4, +// ), +// ), +// child: Padding( +// padding: EdgeInsets.only( +// top: Constants.appBarHeight, +// left: Constants.defaultPadding, +// right: Constants.defaultPadding, +// bottom: Constants.bottomNavBarHeight, +// ), +// child: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// SvgPicture.asset(Assets.iconsWifi), +// SvgPicture.asset(Assets.iconsBatteryIndicator), +// ], +// ), +// SizedBox.square( +// dimension: 255, +// ), +// SizedBox( +// height: 80, +// ), +// GridView.builder( +// shrinkWrap: true, +// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( +// crossAxisCount: 2, +// mainAxisSpacing: 10, +// crossAxisSpacing: 10, +// ), +// itemBuilder: (context, index) => DefaultContainer( +// child: SizedBox.shrink(), +// width: 175, +// height: 100, +// ), +// ), +// ], +// )), +// ), +// ), +// ), +// ); +// } +// } diff --git a/lib/features/scene/bloc/scene_cubit.dart b/lib/features/scene/bloc/scene_cubit.dart index 350173a..f22d3e4 100644 --- a/lib/features/scene/bloc/scene_cubit.dart +++ b/lib/features/scene/bloc/scene_cubit.dart @@ -12,10 +12,8 @@ class SceneCubit extends Cubit { void getScenes() { emit(SceneLoading()); - //TODO: remove this it's causing the Bad State because its being after the cubit is closed - Future.delayed(const Duration(milliseconds: 50), () { - emit(SceneSuccess()); - }); + + emit(SceneSuccess()); } List scenes = []; diff --git a/lib/features/shared_widgets/custom_switch.dart b/lib/features/shared_widgets/custom_switch.dart index cdb9eab..e83a69e 100644 --- a/lib/features/shared_widgets/custom_switch.dart +++ b/lib/features/shared_widgets/custom_switch.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; -import 'package:syncrow_app/features/devices/model/device_category_model.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; @@ -42,13 +41,10 @@ class CustomSwitch extends StatelessWidget { width: 45.0, height: 28.0, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24.0), - color: status != null - ? status - ? ColorsManager.primaryColor - : const Color(0xFFD9D9D9) - : const Color(0xFFD9D9D9), - ), + borderRadius: BorderRadius.circular(24.0), + color: status + ? ColorsManager.primaryColor + : const Color(0xFFD9D9D9)), child: Center( child: Container( width: 40.0, @@ -60,20 +56,16 @@ class CustomSwitch extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(2.0), child: Container( - alignment: status != null - ? status - ? Alignment.centerRight - : Alignment.centerLeft + alignment: status + ? Alignment.centerRight : Alignment.centerLeft, child: Container( width: 20.0, height: 20.0, decoration: BoxDecoration( shape: BoxShape.circle, - color: status != null - ? status - ? ColorsManager.primaryColor - : Colors.grey + color: status + ? ColorsManager.primaryColor : Colors.grey, ), ), diff --git a/lib/my_app.dart b/lib/my_app.dart index 1ec3b08..46ae999 100644 --- a/lib/my_app.dart +++ b/lib/my_app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/theme_manager.dart'; @@ -21,6 +22,8 @@ class MyApp extends StatelessWidget { return BlocProvider( create: (context) => AuthCubit(), child: MaterialApp( + navigatorKey: NavigationService.navigatorKey, + scaffoldMessengerKey: NavigationService.snackbarKey, debugShowCheckedModeBanner: false, color: ColorsManager.primaryColor, title: 'Syncrow App', diff --git a/lib/navigation/navigation_service.dart b/lib/navigation/navigation_service.dart new file mode 100644 index 0000000..c0b1216 --- /dev/null +++ b/lib/navigation/navigation_service.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + static GlobalKey navigatorKey = GlobalKey(); + static GlobalKey? snackbarKey = + GlobalKey(); +} diff --git a/lib/services/api/authentication_api.dart b/lib/services/api/authentication_api.dart index 70f502c..a5e81e4 100644 --- a/lib/services/api/authentication_api.dart +++ b/lib/services/api/authentication_api.dart @@ -16,16 +16,11 @@ class AuthenticationAPI { static Future loginWithEmail( {required LoginWithEmailModel model}) async { - try { - final response = await HTTPService().post( - path: ApiEndpoints.login, - body: model.toJson(), - showServerMessage: false, - expectedResponseModel: (json) => Token.fromJson(json['data'])); - // debugPrint("response: $response"); - return response; - } catch (e) { - rethrow; - } + final response = await HTTPService().post( + path: ApiEndpoints.login, + body: model.toJson(), + showServerMessage: false, + expectedResponseModel: (json) => Token.fromJson(json['data'])); + return response; } } diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index c8c04c4..97c5588 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -1,5 +1,6 @@ import 'package:syncrow_app/features/devices/model/device_category_model.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/services/api/api_links_endpoints.dart'; import 'package:syncrow_app/services/api/http_service.dart'; @@ -8,17 +9,19 @@ class DevicesAPI { static Future> controlDevice( DeviceControlModel controlModel) async { - // print( - // 'contoling [${controlModel.deviceId}] with code [${controlModel.code}] and value [${controlModel.value}'); - final response = await _httpService.post( - path: ApiEndpoints.control, - body: controlModel.toJson(), - showServerMessage: false, - expectedResponseModel: (json) { - return json; - }, - ); - return response; + try { + final response = await _httpService.post( + path: ApiEndpoints.control, + body: controlModel.toJson(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } } static Future> fetchGroups(int spaceId) async { @@ -27,6 +30,7 @@ class DevicesAPI { "pageSize": 100, "pageNo": 1 }; + final response = await _httpService.get( path: ApiEndpoints.groups, queryParameters: params, @@ -47,4 +51,20 @@ class DevicesAPI { ); return response; } + + static Future> getDevicesByRoomId(int roomId) async { + final response = await _httpService.get( + path: ApiEndpoints.devicesByRoom, + queryParameters: {"roomId": roomId, "pageSize": 10}, + showServerMessage: false, + expectedResponseModel: (json) { + List devices = []; + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } } diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index 3460bc4..7f3b8be 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -1,46 +1,84 @@ +import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.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/navigation_service.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'dart:async'; +import 'dart:developer' as developer; +import 'package:syncrow_app/services/api/network_exception.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class HTTPInterceptor extends InterceptorsWrapper { - // @override - // void onResponse(Response response, ResponseInterceptorHandler handler) async { - // // Pass the response to the next interceptor or response handler. - // return handler.next(response); - // } - // + List headerExclusionList = []; + + List headerExclusionListOfAddedParameters = [ + ApiEndpoints.login, + ]; + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + if (await validateResponse(response)) { + super.onResponse(response, handler); + } else { + handler.reject(DioException( + requestOptions: response.requestOptions, response: response)); + } + } + @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { var storage = const FlutterSecureStorage(); var token = await storage.read(key: Token.loginAccessTokenKey); - - options.headers['Authorization'] = 'Bearer $token'; + if (checkHeaderExclusionListOfAddedParameters(options.path)) { + options.headers + .putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); + } + // options.headers['Authorization'] = 'Bearer ${'${token!}123'}'; super.onRequest(options, handler); } -// -// @override -// void onError(DioException err, ErrorInterceptorHandler handler) async { -// // TODO: Implement error handling logic. -// // This method is called when an error occurs during a request. -// super.onError(err, handler); -// } -// -// /// Validates the response and returns true if it is successful (status code 2xx). -// Future validateResponse(Response response) async { -// if (response.statusCode != null) { -// if (response.statusCode! >= 200 && response.statusCode! < 300) { -// // If the response status code is within the successful range (2xx), -// // return true indicating a successful response. -// return true; -// } else { -// // If the response status code is not within the successful range (2xx), -// // return false indicating an unsuccessful response. -// return false; -// } -// } else { -// // If the response status code is null, return false indicating an unsuccessful response. -// return false; -// } -// } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + ServerFailure failure = ServerFailure.fromDioError(err); + CustomSnackBar.displaySnackBar(failure.toString()); + var storage = const FlutterSecureStorage(); + var token = await storage.read(key: Token.loginAccessTokenKey); + if (err.response?.statusCode == 401 && token != null) { + await AuthCubit.get(NavigationService.navigatorKey.currentContext!) + .logout(); + } + super.onError(err, handler); + } + + /// Validates the response and returns true if it is successful (status code 2xx). + Future validateResponse(Response response) async { + if (response.statusCode != null) { + if (response.statusCode! >= 200 && response.statusCode! < 300) { + // If the response status code is within the successful range (2xx), + // return true indicating a successful response. + return true; + } else { + // If the response status code is not within the successful range (2xx), + // return false indicating an unsuccessful response. + return false; + } + } else { + // If the response status code is null, return false indicating an unsuccessful response. + return false; + } + } + + checkHeaderExclusionListOfAddedParameters(String path) { + bool shouldAddHeader = true; + + for (var urlConstant in headerExclusionListOfAddedParameters) { + if (path.contains(urlConstant)) { + shouldAddHeader = false; + } + } + return shouldAddHeader; + } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 9551d4a..dc995ff 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,5 +1,5 @@ import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; +import 'dart:developer' as developer; import 'package:syncrow_app/services/api/api_links_endpoints.dart'; import 'package:syncrow_app/services/api/http_interceptor.dart'; import 'package:syncrow_app/services/locator.dart'; @@ -39,9 +39,6 @@ class HTTPService { ); return expectedResponseModel(response.data); } catch (error) { - debugPrint("******* Error"); - debugPrint(error.toString()); - debugPrint("******* Error End"); rethrow; } } @@ -60,8 +57,6 @@ class HTTPService { queryParameters: queryParameters, options: options, ); - // debugPrint("status code is ${response.statusCode}"); - // debugPrint("response data is ${response.data}"); return expectedResponseModel(response.data); } catch (error) { rethrow; @@ -79,11 +74,8 @@ class HTTPService { data: body, queryParameters: queryParameters, ); - debugPrint("status code is ${response.statusCode}"); return expectedResponseModel(response.data); } catch (error) { - debugPrint("******* Error"); - debugPrint(error.toString()); rethrow; } } @@ -94,26 +86,19 @@ class HTTPService { Map? queryParameters, required T Function(dynamic) expectedResponseModel}) async { try { - debugPrint("download begins"); final response = await client.download( path, savePath, - onReceiveProgress: (current, total) { - debugPrint("current = $current, while total = $total"); - }, + onReceiveProgress: (current, total) {}, ); - debugPrint("download ends"); + return expectedResponseModel(response.data); // return expectedResponseModel(response.data); } catch (error) { - debugPrint("******* Error"); - debugPrint("download error"); - debugPrint(error.toString()); rethrow; } } - //delete Future delete({ required String path, Map? queryParameters, @@ -127,8 +112,6 @@ class HTTPService { ); return expectedResponseModel(response.data); } catch (error) { - debugPrint("******* Error"); - debugPrint(error.toString()); rethrow; } } diff --git a/lib/services/api/my_http_overrides.dart b/lib/services/api/my_http_overrides.dart index 512523c..3707be3 100644 --- a/lib/services/api/my_http_overrides.dart +++ b/lib/services/api/my_http_overrides.dart @@ -5,6 +5,7 @@ class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { return super.createHttpClient(context) - ..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; } } diff --git a/lib/services/api/network_exception.dart b/lib/services/api/network_exception.dart index f8940de..d8f8d23 100644 --- a/lib/services/api/network_exception.dart +++ b/lib/services/api/network_exception.dart @@ -32,9 +32,7 @@ class ServerFailure extends Failure { // var document = parser.parse(dioError.response!.data.toString()); // var message = document.body!.text; return ServerFailure.fromResponse(dioError.response!.statusCode!, - dioError.response!.data['message'].toString() - // message - ); + dioError.response?.data['message'] ?? "Error"); } case DioExceptionType.cancel: return ServerFailure("The request to ApiServer was canceled"); @@ -50,31 +48,27 @@ class ServerFailure extends Failure { } } - factory ServerFailure.fromResponse(int statusCode, dynamic response) { - if (statusCode == 401 || statusCode == 403) { - return ServerFailure(response); - } else if (statusCode == 400) { - //response is list of errors - List errors = []; - response.forEach((element) { - errors.add(element); - }); - return ServerFailure(errors.join('\n')); - } else if (statusCode == 404) { - return ServerFailure("Your request not found, Please try later!"); - } else if (statusCode == 500) { - return ServerFailure(response); - } else { - return ServerFailure("Opps there was an Error, Please try again!"); + factory ServerFailure.fromResponse(int? statusCode, dynamic responseMessage) { + switch (statusCode) { + case 401: + case 403: + return ServerFailure(responseMessage); + case 400: + List errors = []; + if (responseMessage is List) { + for (var error in responseMessage) { + errors.add(error); + } + } else { + return ServerFailure(responseMessage); + } + return ServerFailure(errors.join('\n')); + case 404: + return ServerFailure("Your request not found, Please try later!"); + case 500: + return ServerFailure(responseMessage); + default: + return ServerFailure("Opps there was an Error, Please try again!"); } } } - -class ResponseFailure extends Failure { - ResponseFailure(super.errMessage); - - @override - String toString() { - return errMessage; - } -} diff --git a/lib/services/api/spaces_api.dart b/lib/services/api/spaces_api.dart index ebf2597..3aa95dd 100644 --- a/lib/services/api/spaces_api.dart +++ b/lib/services/api/spaces_api.dart @@ -39,20 +39,4 @@ class SpacesAPI { ); return response; } - - static Future> getDevicesByRoomId(int roomId) async { - final response = await _httpService.get( - path: ApiEndpoints.devicesByRoom, - queryParameters: {"roomId": roomId, "pageSize": 10}, - showServerMessage: false, - expectedResponseModel: (json) { - List devices = []; - for (var device in json['devices']) { - devices.add(DeviceModel.fromJson(device)); - } - return devices; - }, - ); - return response; - } } diff --git a/lib/utils/helpers/misc_string_helpers.dart b/lib/utils/helpers/misc_string_helpers.dart index 030515a..616e8a4 100644 --- a/lib/utils/helpers/misc_string_helpers.dart +++ b/lib/utils/helpers/misc_string_helpers.dart @@ -1,13 +1,11 @@ +import 'dart:developer' as developer; import 'dart:io'; -import 'package:flutter/material.dart'; - class StringHelpers { // TODO ( to Mohammad Salameh): convert this to extension method static String enhanceFileName(File file) { var fileName = " "; final filePath = file.path; - debugPrint(filePath); final fileStringArray = filePath.split("/"); fileName = fileStringArray.last; if (fileName.length > 20) { diff --git a/lib/utils/helpers/snack_bar.dart b/lib/utils/helpers/snack_bar.dart new file mode 100644 index 0000000..61c99ee --- /dev/null +++ b/lib/utils/helpers/snack_bar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; + +class CustomSnackBar { + static displaySnackBar(String message) { + final key = NavigationService.snackbarKey; + if (key != null) { + final snackBar = SnackBar(content: Text(message)); + key.currentState?.clearSnackBars(); + key.currentState?.showSnackBar(snackBar); + } + } + + static greenSnackBar(String message) { + final key = NavigationService.snackbarKey; + BuildContext? currentContext = key?.currentContext; + if (key != null && currentContext != null) { + final snackBar = SnackBar( + padding: const EdgeInsets.all(16), + backgroundColor: Colors.green, + content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon( + Icons.check_circle, + color: Colors.white, + size: 32, + ), + const SizedBox( + width: 8, + ), + Text( + message, + style: Theme.of(currentContext).textTheme.bodySmall!.copyWith( + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), + ) + ]), + ); + key.currentState?.showSnackBar(snackBar); + } + } +} diff --git a/lib/utils/resource_manager/theme_manager.dart b/lib/utils/resource_manager/theme_manager.dart index 56cd784..fdde064 100644 --- a/lib/utils/resource_manager/theme_manager.dart +++ b/lib/utils/resource_manager/theme_manager.dart @@ -207,7 +207,6 @@ abstract class ThemeManager { ), ///card theme - //TODO edit card theme cardTheme: const CardTheme( elevation: 0, shape: RoundedRectangleBorder(