Initialized Auth pages for future work

Implemented Login functionality
This commit is contained in:
Mohammad Salameh
2024-03-07 10:29:19 +03:00
parent 4087f9c71c
commit f734801e94
28 changed files with 728 additions and 110 deletions
+53
View File
@@ -1,7 +1,60 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/model/token.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/services/api/authentication_api.dart';
import 'package:syncrow_app/utils/helpers/decode_base64.dart';
part 'auth_state.dart';
class AuthCubit extends Cubit<AuthState> {
AuthCubit() : super(AuthInitial());
static AuthCubit get(context) => BlocProvider.of(context);
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool isPasswordVisible = false;
void changePasswordVisibility() {
isPasswordVisible = !isPasswordVisible;
emit(AuthPasswordVisibilityChanged());
}
bool agreeToTerms = false;
void changeAgreeToTerms() {
agreeToTerms = !agreeToTerms;
emit(AuthAgreeToTermsChanged());
}
UserModel? user;
Token token = Token.emptyConstructor();
// (FlutterSecureStorage().read(key :'token'));
login() async {
emit(AuthLoading());
try {
token = await AuthenticationAPI.loginWithEmail(
email: emailController.text.toLowerCase(),
password: passwordController.text,
);
emit(AuthSuccess());
} catch (e) {
emit(AuthError(e.toString()));
}
final parts = token.accessToken.split('.');
if (parts.length != 3) {
throw Exception('invalid access token');
}
final payload = decodeBase64(parts[1]);
final payloadMap = json.decode(payload); //Map dictionary
user = UserModel.fromToken(payloadMap);
}
}
+16
View File
@@ -3,3 +3,19 @@ part of 'auth_cubit.dart';
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthError extends AuthState {
final String message;
AuthError(this.message) {
debugPrint(message);
}
}
class AuthSuccess extends AuthState {}
class AuthPasswordVisibilityChanged extends AuthState {}
class AuthAgreeToTermsChanged extends AuthState {}
+31
View File
@@ -0,0 +1,31 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class Token {
static const String loginAccessToken = 'access_token';
static const String loginRefreshToken = 'refreshToken';
final String accessToken;
final String refreshToken;
Token.emptyConstructor()
: accessToken = '',
refreshToken = '';
Token(
this.accessToken,
this.refreshToken,
);
Token.refreshToken(this.refreshToken) : accessToken = '';
factory Token.fromJson(Map<String, dynamic> json) {
//save token to secure storage
var storage = const FlutterSecureStorage();
storage.write(key: loginAccessToken, value: json[loginAccessToken] ?? '');
//create token object ?
return Token(json[loginAccessToken] ?? '', json[loginRefreshToken] ?? '');
}
Map<String, String> toJson() => {loginRefreshToken: refreshToken};
}
+23 -5
View File
@@ -6,19 +6,26 @@ class UserModel {
final String? phoneNumber;
final bool? isAnonymous;
final bool? isEmailVerified;
final bool? isAgreementAccepted;
//token decoded with jwt
//{
// "email": "[email protected]",
// "userId": 2,
// "uuid": "e145438c-4c62-4535-a0f4-f77958f9f9f4",
// "sessionId": "0409a7a1-6ef5-42c5-b3a1-1f15c639b301",
// "iat": 1709711675,
// "exp": 1709711975
// }
UserModel({
required this.id,
required this.email,
required this.name,
required this.photoUrl,
required this.phoneNumber,
required this.isAnonymous,
required this.isEmailVerified,
required this.isAgreementAccepted,
});
@@ -30,12 +37,24 @@ class UserModel {
name: json['name'],
photoUrl: json['photoUrl'],
phoneNumber: json['phoneNumber'],
isAnonymous: json['isAnonymous'],
isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'],
);
}
//from token
factory UserModel.fromToken(Map<String, dynamic> json) {
return UserModel(
id: json['userId'].toString(),
email: json['email'],
name: null,
photoUrl: null,
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
@@ -43,7 +62,6 @@ class UserModel {
'name': name,
'photoUrl': photoUrl,
'phoneNumber': phoneNumber,
'isAnonymous': isAnonymous,
'isEmailVerified': isEmailVerified,
'isAgreementAccepted': isAgreementAccepted,
};
+27
View File
@@ -0,0 +1,27 @@
class VerifyPassCode {
static const String verificationPhone = 'phone';
static const String verificationPassCode = 'passCode';
static const String verificationAgent = 'agent';
static const String verificationDeviceId = 'deviceId';
final String phone;
final String passCode;
final String agent;
final String deviceId;
VerifyPassCode(
{required this.phone, required this.passCode, required this.agent, required this.deviceId});
factory VerifyPassCode.fromJson(Map<String, dynamic> json) => VerifyPassCode(
phone: json[verificationPhone],
passCode: json[verificationPassCode],
agent: json[verificationAgent],
deviceId: json[verificationDeviceId]);
Map<String, dynamic> toJson() => {
verificationPhone: phone,
verificationPassCode: passCode,
verificationAgent: agent,
verificationDeviceId: deviceId,
};
}
@@ -22,7 +22,7 @@ class AuthViewBody extends StatelessWidget {
DefaultTextButton(
text: 'Login',
onPressed: () {
Navigator.popAndPushNamed(context, Routes.homeRoute);
Navigator.pushNamed(context, Routes.authLogin);
},
),
const SizedBox(height: 15),
@@ -31,18 +31,6 @@ class AuthViewBody extends StatelessWidget {
isSecondary: true,
),
const SizedBox(height: 20),
Center(
child: InkWell(
onTap: () {},
child: const Text(
'Try as a Guest',
style: TextStyle(
color: Colors.grey,
),
),
),
),
const SizedBox(height: 30),
],
),
);
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class DidntGetCodeView extends StatelessWidget {
const DidntGetCodeView({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
@@ -0,0 +1,74 @@
import 'package:flutter/gestures.dart';
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/routing_constants.dart';
import 'package:syncrow_app/utils/context_extension.dart';
class LoginUserAgreement extends StatelessWidget {
const LoginUserAgreement({
super.key,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return Row(
children: [
Checkbox(
value: AuthCubit.get(context).agreeToTerms,
onChanged: (value) => AuthCubit.get(context).changeAgreeToTerms(),
),
Expanded(
child: RichText(
softWrap: true,
maxLines: 2,
text: TextSpan(
text: 'I Agree to the ',
style: context.bodySmall,
children: [
TextSpan(
text: 'Privacy Policy',
style: context.bodySmall.copyWith(
color: Colors.blue,
decoration: TextDecoration.underline,
decorationColor: Colors.blue,
),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.pushNamed(context, Routes.policyRoute);
},
),
TextSpan(
text: ' and ',
style: context.bodySmall,
),
TextSpan(
text: 'User Agreement',
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.pushNamed(context, Routes.termsRoute);
},
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
decorationColor: Colors.blue,
),
),
const TextSpan(
text: '.',
style: TextStyle(
color: Colors.black,
),
),
],
),
),
),
],
);
},
);
}
}
@@ -0,0 +1,116 @@
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/features/auth/view/widgets/login/login_user_agreement.dart';
import 'package:syncrow_app/features/shared_widgets/default_text_button.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
class LoginView extends StatelessWidget {
const LoginView({super.key});
@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.pushNamed(context, Routes.homeRoute);
}
},
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Constants.defaultPadding,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const TitleMedium(
text: 'Login',
),
const SizedBox(
height: 10,
),
TextField(
controller: AuthCubit.get(context).emailController,
decoration: InputDecoration(
labelText: 'Email',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: ColorsManager.backgroundColor.withAlpha(100),
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: const EdgeInsets.all(10),
),
),
const SizedBox(
height: 10,
),
TextField(
controller: AuthCubit.get(context).passwordController,
obscureText: !AuthCubit.get(context).isPasswordVisible,
decoration: InputDecoration(
labelText: 'Password',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(10),
),
suffixIcon: IconButton(
icon: Icon(
AuthCubit.get(context).isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
AuthCubit.get(context).changePasswordVisibility();
},
),
filled: true,
fillColor: ColorsManager.backgroundColor.withAlpha(100),
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: const EdgeInsets.all(10),
),
),
const SizedBox(height: 10),
const LoginUserAgreement(),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
state is AuthLoading
? const CircularProgressIndicator()
: Expanded(
child: DefaultTextButton(
enabled: AuthCubit.get(context).agreeToTerms,
text: "Login",
onPressed: () {
AuthCubit.get(context).login();
FocusScope.of(context).unfocus();
},
),
),
],
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class OneTimePasswordView extends StatelessWidget {
const OneTimePasswordView({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class PrivacyPolicyView extends StatelessWidget {
const PrivacyPolicyView({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class SignUpView extends StatelessWidget {
const SignUpView({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class UserAgreementView extends StatelessWidget {
const UserAgreementView({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
@@ -9,10 +9,10 @@ class LightsCubit extends Cubit<LightsState> {
static LightsCubit get(context) => BlocProvider.of(context);
Map<int, LightMode> lightModes = {
0: LightMode.Doze,
1: LightMode.Relax,
2: LightMode.Reading,
3: LightMode.Energizing,
0: LightMode.doze,
1: LightMode.relax,
2: LightMode.reading,
3: LightMode.energizing,
};
setLightingMode(LightModel light, LightMode mode) {
@@ -55,8 +55,8 @@ class LightsCubit extends Cubit<LightsState> {
}
enum LightMode {
Doze,
Relax,
Reading,
Energizing,
doze,
relax,
reading,
energizing,
}
+5 -4
View File
@@ -12,10 +12,11 @@ class CategoriesView extends StatelessWidget {
create: (context) => DevicesCubit(),
child: BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) => Container(
padding: const EdgeInsets.all(8),
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: const DevicesViewBody()),
padding: const EdgeInsets.all(8),
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: const DevicesViewBody(),
),
),
);
}
@@ -4,6 +4,7 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class DefaultTextButton extends StatelessWidget {
const DefaultTextButton({
super.key,
this.enabled = true,
this.onPressed,
required this.text,
this.isSecondary = false,
@@ -13,15 +14,17 @@ class DefaultTextButton extends StatelessWidget {
final String text;
final bool isSecondary;
final bool enabled;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: onPressed,
onPressed: enabled ? onPressed : null,
style: isSecondary
? null
: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(ColorsManager.primaryColor),
backgroundColor: MaterialStateProperty.all(
enabled ? ColorsManager.primaryColor : Colors.grey),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
@@ -30,7 +33,12 @@ class DefaultTextButton extends StatelessWidget {
),
child: Text(
text,
style: TextStyle(color: isSecondary ? Colors.black : Colors.white),
style: TextStyle(
color: isSecondary
? Colors.black
: enabled
? Colors.white
: Colors.black),
),
);
}
+2
View File
@@ -33,9 +33,11 @@ class Assets {
static const String iconsMenuFill = 'assets/icons/Menu-fill.svg';
static const String iconsMinus = 'assets/icons/minus.svg';
static const String iconsPlus = 'assets/icons/plus.svg';
static const String iconsQrScan = 'assets/icons/qr_scan.svg';
static const String iconsRoutines = 'assets/icons/Routines.svg';
static const String iconsRoutinesFill = 'assets/icons/Routines-fill.svg';
static const String iconsScreen = 'assets/icons/Screen.svg';
static const String iconsSettings = 'assets/icons/settings.svg';
static const String iconsSummer = 'assets/icons/Summer.svg';
static const String iconsSummerMode = 'assets/icons/summer_mode.svg';
static const String iconsSunnyMode = 'assets/icons/sunnyMode.svg';
+31 -1
View File
@@ -1,9 +1,15 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/app_layout/view/app_layout.dart';
import 'package:syncrow_app/features/auth/view/auth_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/didnt_get_code/didnt_get_code_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/one_time_password/one_time_password_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/privacy_policy/privacy_policy_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/sign_up/sign_up_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/user_agreement/user_agreement_view.dart';
import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart';
import 'package:syncrow_app/features/devices/view/devices_view.dart';
import 'package:syncrow_app/features/layout/view/layout_view.dart';
import 'package:syncrow_app/features/app_layout/view/app_layout.dart';
import 'package:syncrow_app/features/profile/view/profile_view.dart';
import 'package:syncrow_app/features/scene/view/scene_view.dart';
import 'package:syncrow_app/features/splash/view/splash_view.dart';
@@ -37,6 +43,30 @@ class Router {
return MaterialPageRoute(
builder: (_) => const AuthView(), settings: settings);
case Routes.authLogin:
return MaterialPageRoute(
builder: (_) => const LoginView(), settings: settings);
case Routes.authOneTimePassword:
return MaterialPageRoute(
builder: (_) => const OneTimePasswordView(), settings: settings);
case Routes.authSignUp:
return MaterialPageRoute(
builder: (_) => const SignUpView(), settings: settings);
case Routes.policyRoute:
return MaterialPageRoute(
builder: (_) => const PrivacyPolicyView(), settings: settings);
case Routes.termsRoute:
return MaterialPageRoute(
builder: (_) => const UserAgreementView(), settings: settings);
case Routes.authDidNotGetCode:
return MaterialPageRoute(
builder: (_) => const DidntGetCodeView(), settings: settings);
case Routes.dashboardRoute:
return MaterialPageRoute(
builder: (_) => const DashboardView(), settings: settings);
+7
View File
@@ -7,4 +7,11 @@ class Routes {
static const String layoutRoute = '/layout';
static const String profileRoute = '/profile';
static const String authRoute = '/auth';
static const String authLogin = '$authRoute/login';
static const String authSignUp = '$authRoute/signup';
static const String authOneTimePassword = '$authRoute/one-time-password';
static const String authForgotPassword = '$authRoute/forgot-password';
static const String authDidNotGetCode = '$authRoute/did-not-get-code';
static const String policyRoute = '/policy';
static const String termsRoute = '/terms';
}
+9 -1
View File
@@ -1,3 +1,11 @@
abstract class ApiEndpoints {
static const String apiKey = '';
static const String baseUrl = 'http://100.107.182.63:4001';
static const String signUp = '$baseUrl/authentication/user/signup';
static const String login = '$baseUrl/authentication/user/login';
static const String deleteUser = '$baseUrl/authentication/user/delete/{id}';
static const String sendOtp = '$baseUrl/authentication/user/send-otp';
static const String verifyOtp = '$baseUrl/authentication/user/verify-otp';
static const String forgetPassword =
'$baseUrl/authentication/user/forget-password';
}
+140
View File
@@ -0,0 +1,140 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:syncrow_app/features/auth/model/token.dart';
import 'package:syncrow_app/features/auth/model/verify_code.dart';
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
import 'package:syncrow_app/services/api/http_service.dart';
class AuthenticationAPI {
static Future<Token> verifyPassCode(VerifyPassCode data) async {
final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp,
body: data.toJson(),
showServerMessage: false,
expectedResponseModel: (json) {
Token token = Token.fromJson(json);
return token;
});
return response;
}
static Future<Token> loginWithEmail(
{required String email, required String password}) async {
final response = await HTTPService().post(
path: ApiEndpoints.login,
body: jsonEncode({
"email": email,
"password": password,
}),
showServerMessage: false,
expectedResponseModel: (json) {
Token token = Token.fromJson(json['data']);
return token;
});
debugPrint("response: $response");
return response;
}
// static Future<SuccessResponse> updateUserInfo(
// Map<String, dynamic> data) async {
// final response = await HTTPService().postRequest(
// path: APIConstants.updateUserInfo,
// body: data,
// expectedResponseModel: (json) {
// SuccessResponse token = SuccessResponse.fromJson(json);
// return token;
// });
// return response;
// }
//
// static Future<LoginWithPhoneResponse> loginWithPhone(
// {required LoginWithPhone data, String? recaptchaToken}) async {
// final response = await HTTPService().postRequest(
// path: APIConstants.loginWithChannel,
// body: data.toJson(),
// options: Options(headers: {
// 'captcha-token': recaptchaToken,
// 'captcha-site-key': Platform.isAndroid
// ? dotenv.env['RECAPTCH_ANDROID_SITE_KEY'] ?? ''
// : dotenv.env['RECAPTCH_IOS_SITE_KEY'] ?? ''
// }),
// expectedResponseModel: (json) {
// LoginWithPhoneResponse result = LoginWithPhoneResponse.fromJson(json);
// return result;
// });
// return response;
// }
//
// static Future<List<dynamic>> countryCodes() async {
// final response = await HTTPService().postRequest(
// path: APIConstants.getCountyCode,
// expectedResponseModel: (json) {
// List<dynamic> result = json.toList();
// return result;
// });
// return response;
// }
//
// static Future<bool> sendNotificationToken({
// required Map<String, String> data,
// }) async {
// final response = await HTTPService().postRequest(
// path: APIConstants.notificationToken,
// body: data,
// showServerMessage: false,
// expectedResponseModel: (json) {
// bool checked = false;
// if (json != null) {
// if (json['success']) {
// checked = true;
// }
// }
// return checked;
// });
// return response;
// }
//
// static Future<bool> logout() async {
// final response = await HTTPService().postRequest(
// path: APIConstants.logout,
// expectedResponseModel: (json) {
// bool checked = false;
// // print(json);
// if (json != null) {
// if (json['success']) {
// checked = true;
// }
// }
// return checked;
// });
// return response;
// }
//
// static Future<bool> deleteAccount() async {
// final response = await HTTPService().postRequest(
// path: APIConstants.deleteAccount,
// expectedResponseModel: (json) {
// bool checked = false;
// if (json != null) {
// if (json['success']) {
// checked = true;
// }
// }
// return checked;
// });
// return response;
// }
// static Future<Token> refreshToken(Map<String, dynamic> data) async {
// final response = await HTTPService().postRequest(
// path: APIConstants.refreshToken,
// showServerMessage: false,
// body: data,
// expectedResponseModel: (json) {
// Token token = Token.fromJson(json);
// return token;
// });
// return response;
// }
}
+38 -40
View File
@@ -1,44 +1,42 @@
import 'package:dio/dio.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);
}
@override
void onRequest(RequestOptions options,
RequestInterceptorHandler handler) async {
// TODO: Implement logic for adding headers to requests.
// This method is called before a request is sent.
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 onResponse(Response response, ResponseInterceptorHandler handler) async {
// // Pass the response to the next interceptor or response handler.
// return handler.next(response);
// }
//
// @override
// void onRequest(RequestOptions options,
// RequestInterceptorHandler handler) async {
// // TODO: Implement logic for adding headers to requests.
// // This method is called before a request is sent.
// 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;
// }
// }
}
+39 -27
View File
@@ -1,42 +1,32 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
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';
import 'http_interceptor.dart';
class HTTPService {
final Dio client = locator<Dio>();
final navigatorKey = GlobalKey<NavigatorState>();
late Dio client = serviceLocator.get<Dio>();
// final navigatorKey = GlobalKey<NavigatorState>();
String certificateString = "";
static Dio setupDioClient() {
final client = Dio(
Dio setupDioClient() {
client = Dio(
BaseOptions(
// TODO add base url
// baseUrl: URLConstants.baseURL,
baseUrl: ApiEndpoints.baseUrl,
receiveDataWhenStatusError: true,
followRedirects: false,
connectTimeout: const Duration(milliseconds: 60000),
receiveTimeout: const Duration(milliseconds: 60000),
connectTimeout: const Duration(minutes: 1),
receiveTimeout: const Duration(minutes: 1),
),
);
// (client.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
// client. = (X509Certificate cert, String host, int port) {
// // TODO add SSL certificate
// // if(cert.pem == certificateString){ // Verify the certificate
// // return true;
// // }
// // return false;
// return true;
// };
// return client;
// };
client.interceptors.add(locator<HTTPInterceptor>());
client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
return client;
}
Future<T> getRequest<T>({
Future<T> get<T>({
required String path,
Map<String, dynamic>? queryParameters,
required T Function(dynamic) expectedResponseModel,
@@ -55,7 +45,7 @@ class HTTPService {
}
}
Future<T> postRequest<T>(
Future<T> post<T>(
{required String path,
Map<String, dynamic>? queryParameters,
Options? options,
@@ -65,16 +55,18 @@ class HTTPService {
try {
final response = await client.post(path,
data: body, queryParameters: queryParameters, options: options);
print("post response is $response");
debugPrint("status code is ${response.statusCode}");
debugPrint("response data is ${response.data}");
return expectedResponseModel(response.data);
} catch (error) {
debugPrint("******* Error");
debugPrint("******* Error ********");
debugPrint(error.toString());
rethrow;
}
}
Future<T> patchRequest<T>(
Future<T> patch<T>(
{required String path,
Map<String, dynamic>? queryParameters,
dynamic body,
@@ -94,7 +86,7 @@ class HTTPService {
}
}
Future<T> downloadRequest<T>(
Future<T> download<T>(
{required String path,
required String savePath,
Map<String, dynamic>? queryParameters,
@@ -118,4 +110,24 @@ class HTTPService {
rethrow;
}
}
//delete
Future<T> delete<T>({
required String path,
Map<String, dynamic>? queryParameters,
required T Function(dynamic) expectedResponseModel,
bool showServerMessage = true,
}) async {
try {
final response = await client.delete(
path,
queryParameters: queryParameters,
);
return expectedResponseModel(response.data);
} catch (error) {
debugPrint("******* Error");
debugPrint(error.toString());
rethrow;
}
}
}
+6 -7
View File
@@ -1,14 +1,13 @@
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:syncrow_app/services/api/http_interceptor.dart';
import 'package:syncrow_app/services/api/http_service.dart';
import 'api/http_interceptor.dart';
GetIt locator = GetIt.instance;
final GetIt serviceLocator = GetIt.instance;
// setupLocator() // to search for dependency injection in flutter
initialSetup() async {
locator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
initialSetup() {
serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
//Base classes
locator.registerSingleton<Dio>(HTTPService.setupDioClient());
serviceLocator.registerSingleton<Dio>(HTTPService().setupDioClient());
}
+21
View File
@@ -0,0 +1,21 @@
import 'dart:convert';
String decodeBase64(String str) {
//'-', '+' 62nd char of encoding, '_', '/' 63rd char of encoding
String output = str.replaceAll('-', '+').replaceAll('_', '/');
switch (output.length % 4) {
// Pad with trailing '='
case 0: // No pad chars in this case
break;
case 2: // Two pad chars
output += '==';
break;
case 3: // One pad char
output += '=';
break;
default:
throw Exception('Illegal base64url string!"');
}
return utf8.decode(base64Url.decode(output));
}
@@ -2,4 +2,8 @@ abstract class Constants {
static const String languageCode = "en";
static const String countryCode = "US";
static const double defaultPadding = 16.0;
static const String tokenKey = 'userToken';
}