mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2026-10-23 11:07:55 +00:00
Merge pull request #13 from SyncrowIOT/token_validation
Refactor HTTPInterceptor and add CustomSnackBar helper
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;
|
||||
@@ -118,14 +125,17 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
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<HomeState> {
|
||||
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<String, List<Widget>> appBarActions = {
|
||||
@@ -166,14 +173,6 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
),
|
||||
],
|
||||
'Devices': [
|
||||
// IconButton(
|
||||
// icon: Image.asset(
|
||||
// Assets.iconsFilter,
|
||||
// height: 20,
|
||||
// width: 20,
|
||||
// ),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
|
||||
@@ -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<SpaceModel> 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<RoomModel> 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;
|
||||
|
||||
|
||||
@@ -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),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,16 @@ class AppBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<HomeCubit, HomeState>(
|
||||
return BlocConsumer<HomeCubit, HomeState>(
|
||||
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<HomeCubit, HomeState>(
|
||||
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()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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<AuthState> {
|
||||
|
||||
static AuthCubit get(context) => BlocProvider.of(context);
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final loginFormKey = GlobalKey<FormState>();
|
||||
bool isPasswordVisible = false;
|
||||
|
||||
static GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
@@ -42,8 +43,58 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
|
||||
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<AuthState> {
|
||||
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<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 +155,10 @@ 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ class LoginForm extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var formKey = GlobalKey<FormState>();
|
||||
var pressed = false;
|
||||
return BlocBuilder<AuthCubit, AuthState>(
|
||||
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: "[email protected]"),
|
||||
),
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -18,18 +18,16 @@ class LoginView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//TODO move to strings manager
|
||||
|
||||
return BlocConsumer<AuthCubit, AuthState>(
|
||||
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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -255,101 +251,92 @@ class DevicesCubit extends Cubit<DevicesState> {
|
||||
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<StatusModel> statuses = [];
|
||||
try {
|
||||
emitSafe(GetDeviceStatusLoading(code: code));
|
||||
int deviceIndex = HomeCubit.getInstance()
|
||||
.selectedSpace!
|
||||
.rooms![roomIndex]
|
||||
.devices!
|
||||
.indexWhere((element) => element.id == deviceId);
|
||||
|
||||
List<StatusModel> 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
|
||||
|
||||
@@ -27,11 +27,11 @@ class AcInterface extends StatelessWidget {
|
||||
child: BlocConsumer<DevicesCubit, DevicesState>(
|
||||
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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -19,25 +19,25 @@ class DevicesViewBody extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<DevicesCubit, DevicesState>(
|
||||
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 ||
|
||||
|
||||
@@ -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,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@@ -12,10 +12,8 @@ class SceneCubit extends Cubit<SceneState> {
|
||||
|
||||
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<SceneModel> scenes = [];
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavigationService {
|
||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
static GlobalKey<ScaffoldMessengerState>? snackbarKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
}
|
||||
@@ -16,16 +16,11 @@ class AuthenticationAPI {
|
||||
|
||||
static Future<Token> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Map<String, dynamic>> 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<List<DevicesCategoryModel>> 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<List<DeviceModel>> getDevicesByRoomId(int roomId) async {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.devicesByRoom,
|
||||
queryParameters: {"roomId": roomId, "pageSize": 10},
|
||||
showServerMessage: false,
|
||||
expectedResponseModel: (json) {
|
||||
List<DeviceModel> devices = [];
|
||||
for (var device in json['devices']) {
|
||||
devices.add(DeviceModel.fromJson(device));
|
||||
}
|
||||
return devices;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> headerExclusionList = [];
|
||||
|
||||
List<String> 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<bool> 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<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic>? 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<T> delete<T>({
|
||||
required String path,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
@@ -127,8 +112,6 @@ class HTTPService {
|
||||
);
|
||||
return expectedResponseModel(response.data);
|
||||
} catch (error) {
|
||||
debugPrint("******* Error");
|
||||
debugPrint(error.toString());
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,20 +39,4 @@ class SpacesAPI {
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<List<DeviceModel>> getDevicesByRoomId(int roomId) async {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.devicesByRoom,
|
||||
queryParameters: {"roomId": roomId, "pageSize": 10},
|
||||
showServerMessage: false,
|
||||
expectedResponseModel: (json) {
|
||||
List<DeviceModel> devices = [];
|
||||
for (var device in json['devices']) {
|
||||
devices.add(DeviceModel.fromJson(device));
|
||||
}
|
||||
return devices;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,6 @@ abstract class ThemeManager {
|
||||
),
|
||||
|
||||
///card theme
|
||||
//TODO edit card theme
|
||||
cardTheme: const CardTheme(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
Reference in New Issue
Block a user