diff --git a/assets/icons/qr_scan.svg b/assets/icons/qr_scan.svg
new file mode 100644
index 0000000..ba9e926
--- /dev/null
+++ b/assets/icons/qr_scan.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/icons/settings.svg b/assets/icons/settings.svg
new file mode 100644
index 0000000..5df067d
--- /dev/null
+++ b/assets/icons/settings.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart
index 1a4fd4f..c85aed0 100644
--- a/lib/features/auth/bloc/auth_cubit.dart
+++ b/lib/features/auth/bloc/auth_cubit.dart
@@ -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 {
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);
+ }
}
diff --git a/lib/features/auth/bloc/auth_state.dart b/lib/features/auth/bloc/auth_state.dart
index 6c008c2..a48552c 100644
--- a/lib/features/auth/bloc/auth_state.dart
+++ b/lib/features/auth/bloc/auth_state.dart
@@ -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 {}
diff --git a/lib/features/auth/model/token.dart b/lib/features/auth/model/token.dart
new file mode 100644
index 0000000..95f45eb
--- /dev/null
+++ b/lib/features/auth/model/token.dart
@@ -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 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 toJson() => {loginRefreshToken: refreshToken};
+}
diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart
index 87049a7..bb052da 100644
--- a/lib/features/auth/model/user_model.dart
+++ b/lib/features/auth/model/user_model.dart
@@ -6,19 +6,26 @@ class UserModel {
final String? phoneNumber;
- final bool? isAnonymous;
-
final bool? isEmailVerified;
final bool? isAgreementAccepted;
+ //token decoded with jwt
+ //{
+ // "email": "Test@Test.com",
+ // "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 json) {
+ return UserModel(
+ id: json['userId'].toString(),
+ email: json['email'],
+ name: null,
+ photoUrl: null,
+ phoneNumber: null,
+ isEmailVerified: null,
+ isAgreementAccepted: null,
+ );
+ }
+
Map toJson() {
return {
'id': id,
@@ -43,7 +62,6 @@ class UserModel {
'name': name,
'photoUrl': photoUrl,
'phoneNumber': phoneNumber,
- 'isAnonymous': isAnonymous,
'isEmailVerified': isEmailVerified,
'isAgreementAccepted': isAgreementAccepted,
};
diff --git a/lib/features/auth/model/verify_code.dart b/lib/features/auth/model/verify_code.dart
new file mode 100644
index 0000000..da29c25
--- /dev/null
+++ b/lib/features/auth/model/verify_code.dart
@@ -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 json) => VerifyPassCode(
+ phone: json[verificationPhone],
+ passCode: json[verificationPassCode],
+ agent: json[verificationAgent],
+ deviceId: json[verificationDeviceId]);
+
+ Map toJson() => {
+ verificationPhone: phone,
+ verificationPassCode: passCode,
+ verificationAgent: agent,
+ verificationDeviceId: deviceId,
+ };
+}
diff --git a/lib/features/auth/view/widgets/auth_view_body.dart b/lib/features/auth/view/widgets/auth_view_body.dart
index 9a85799..0673a87 100644
--- a/lib/features/auth/view/widgets/auth_view_body.dart
+++ b/lib/features/auth/view/widgets/auth_view_body.dart
@@ -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),
],
),
);
diff --git a/lib/features/auth/view/widgets/didnt_get_code/didnt_get_code_view.dart b/lib/features/auth/view/widgets/didnt_get_code/didnt_get_code_view.dart
new file mode 100644
index 0000000..cc1ec63
--- /dev/null
+++ b/lib/features/auth/view/widgets/didnt_get_code/didnt_get_code_view.dart
@@ -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();
+ }
+}
diff --git a/lib/features/auth/view/widgets/login/login_user_agreement.dart b/lib/features/auth/view/widgets/login/login_user_agreement.dart
new file mode 100644
index 0000000..a4e7ee8
--- /dev/null
+++ b/lib/features/auth/view/widgets/login/login_user_agreement.dart
@@ -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(
+ 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,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/features/auth/view/widgets/login/login_view.dart b/lib/features/auth/view/widgets/login/login_view.dart
new file mode 100644
index 0000000..fd4e35b
--- /dev/null
+++ b/lib/features/auth/view/widgets/login/login_view.dart
@@ -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(
+ 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();
+ },
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/features/auth/view/widgets/one_time_password/one_time_password_view.dart b/lib/features/auth/view/widgets/one_time_password/one_time_password_view.dart
new file mode 100644
index 0000000..1c24b91
--- /dev/null
+++ b/lib/features/auth/view/widgets/one_time_password/one_time_password_view.dart
@@ -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();
+ }
+}
diff --git a/lib/features/auth/view/widgets/privacy_policy/privacy_policy_view.dart b/lib/features/auth/view/widgets/privacy_policy/privacy_policy_view.dart
new file mode 100644
index 0000000..6516769
--- /dev/null
+++ b/lib/features/auth/view/widgets/privacy_policy/privacy_policy_view.dart
@@ -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();
+ }
+}
diff --git a/lib/features/auth/view/widgets/sign_up/sign_up_view.dart b/lib/features/auth/view/widgets/sign_up/sign_up_view.dart
new file mode 100644
index 0000000..ddd94b2
--- /dev/null
+++ b/lib/features/auth/view/widgets/sign_up/sign_up_view.dart
@@ -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();
+ }
+}
diff --git a/lib/features/auth/view/widgets/user_agreement/user_agreement_view.dart b/lib/features/auth/view/widgets/user_agreement/user_agreement_view.dart
new file mode 100644
index 0000000..f22c7a9
--- /dev/null
+++ b/lib/features/auth/view/widgets/user_agreement/user_agreement_view.dart
@@ -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();
+ }
+}
diff --git a/lib/features/devices/bloc/lights/lights_cubit.dart b/lib/features/devices/bloc/lights/lights_cubit.dart
index 0ff1f73..3df5821 100644
--- a/lib/features/devices/bloc/lights/lights_cubit.dart
+++ b/lib/features/devices/bloc/lights/lights_cubit.dart
@@ -9,10 +9,10 @@ class LightsCubit extends Cubit {
static LightsCubit get(context) => BlocProvider.of(context);
Map 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 {
}
enum LightMode {
- Doze,
- Relax,
- Reading,
- Energizing,
+ doze,
+ relax,
+ reading,
+ energizing,
}
diff --git a/lib/features/devices/view/devices_view.dart b/lib/features/devices/view/devices_view.dart
index e07b786..69358a1 100644
--- a/lib/features/devices/view/devices_view.dart
+++ b/lib/features/devices/view/devices_view.dart
@@ -12,10 +12,11 @@ class CategoriesView extends StatelessWidget {
create: (context) => DevicesCubit(),
child: BlocBuilder(
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(),
+ ),
),
);
}
diff --git a/lib/features/shared_widgets/default_text_button.dart b/lib/features/shared_widgets/default_text_button.dart
index 6ee35de..db6bf49 100644
--- a/lib/features/shared_widgets/default_text_button.dart
+++ b/lib/features/shared_widgets/default_text_button.dart
@@ -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),
),
);
}
diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart
index ef13fa5..0c2d940 100644
--- a/lib/generated/assets.dart
+++ b/lib/generated/assets.dart
@@ -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';
diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart
index 3f4ad07..9272f3f 100644
--- a/lib/navigation/router.dart
+++ b/lib/navigation/router.dart
@@ -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);
diff --git a/lib/navigation/routing_constants.dart b/lib/navigation/routing_constants.dart
index 1f5ec34..d8cd7a5 100644
--- a/lib/navigation/routing_constants.dart
+++ b/lib/navigation/routing_constants.dart
@@ -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';
}
diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart
index decc473..592fffc 100644
--- a/lib/services/api/api_links_endpoints.dart
+++ b/lib/services/api/api_links_endpoints.dart
@@ -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';
}
diff --git a/lib/services/api/authentication_api.dart b/lib/services/api/authentication_api.dart
new file mode 100644
index 0000000..4f8fea0
--- /dev/null
+++ b/lib/services/api/authentication_api.dart
@@ -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 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 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 updateUserInfo(
+// Map 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 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> countryCodes() async {
+// final response = await HTTPService().postRequest(
+// path: APIConstants.getCountyCode,
+// expectedResponseModel: (json) {
+// List result = json.toList();
+// return result;
+// });
+// return response;
+// }
+//
+// static Future sendNotificationToken({
+// required Map 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 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 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 refreshToken(Map 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;
+// }
+}
diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart
index a818f87..a247d35 100644
--- a/lib/services/api/http_interceptor.dart
+++ b/lib/services/api/http_interceptor.dart
@@ -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 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 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;
+ // }
+ // }
}
diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart
index 7e574f3..ffbf72f 100644
--- a/lib/services/api/http_service.dart
+++ b/lib/services/api/http_service.dart
@@ -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();
- final navigatorKey = GlobalKey();
+ late Dio client = serviceLocator.get();
+
+ // final navigatorKey = GlobalKey();
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());
+
+ client.interceptors.add(serviceLocator.get());
return client;
}
- Future getRequest({
+ Future get({
required String path,
Map? queryParameters,
required T Function(dynamic) expectedResponseModel,
@@ -55,7 +45,7 @@ class HTTPService {
}
}
- Future postRequest(
+ Future post(
{required String path,
Map? 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 patchRequest(
+ Future patch(
{required String path,
Map? queryParameters,
dynamic body,
@@ -94,7 +86,7 @@ class HTTPService {
}
}
- Future downloadRequest(
+ Future download(
{required String path,
required String savePath,
Map? queryParameters,
@@ -118,4 +110,24 @@ class HTTPService {
rethrow;
}
}
+
+ //delete
+ Future delete({
+ required String path,
+ Map? 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;
+ }
+ }
}
diff --git a/lib/services/locator.dart b/lib/services/locator.dart
index 6cbe235..56b4cf0 100644
--- a/lib/services/locator.dart
+++ b/lib/services/locator.dart
@@ -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());
+initialSetup() {
+ serviceLocator.registerSingleton(HTTPInterceptor());
//Base classes
- locator.registerSingleton(HTTPService.setupDioClient());
+
+ serviceLocator.registerSingleton(HTTPService().setupDioClient());
}
diff --git a/lib/utils/helpers/decode_base64.dart b/lib/utils/helpers/decode_base64.dart
new file mode 100644
index 0000000..e047399
--- /dev/null
+++ b/lib/utils/helpers/decode_base64.dart
@@ -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));
+}
diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart
index 34e955e..7a2f837 100644
--- a/lib/utils/resource_manager/constants.dart
+++ b/lib/utils/resource_manager/constants.dart
@@ -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';
}