Skip to content

Commit

Permalink
Merge pull request #245 from Eldar2021/ai/auth
Browse files Browse the repository at this point in the history
Integrated Email Otp Authentication
  • Loading branch information
Eldar2021 authored Aug 16, 2024
2 parents 69a919c + 920dcdb commit a3cbe44
Show file tree
Hide file tree
Showing 35 changed files with 572 additions and 142 deletions.
44 changes: 44 additions & 0 deletions app/lib/app/data/repository/auth_repositoty_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,50 @@ final class AuthRepositoryImpl implements AuthRepository {
}
}

@override
Future<void> loginWithEmail(String email) async {
try {
await remoteDataSource.loginWithEmail(email);
} catch (e, s) {
MqCrashlytics.report(e, s);
log('signWithEmail: error: $e\n$s');
}
}

@override
Future<Either<UserEntity, Exception>> verifyOtp({
required String email,
required String otp,
required String languageCode,
required Gender gender,
}) async {
try {
final res = await remoteDataSource.verifyOtp(
email: email,
otp: otp,
languageCode: languageCode,
gender: gender,
);

return res.fold(
Left.new,
(r) {
final userEntity = UserEntity(
accessToken: r.accessToken,
username: r.username,
gender: r.gender,
localeCode: r.localeCode,
);
return Right(userEntity);
},
);
} catch (e, s) {
log('signWithemail: error: $e\n$s');
MqCrashlytics.report(e, s);
return Left(AuthenticationExc(message: e.toString()));
}
}

@override
Future<Either<UserEntity, Exception>> signWithGoogle(
String languageCode,
Expand Down
5 changes: 3 additions & 2 deletions app/lib/app/data/source/auth_remote_data_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import 'package:my_quran/app/app.dart';
abstract class AuthRemoteDataSource {
Future<void> loginWithEmail(String email);

Future<Either<UserModelResponse, Exception>> fetchSmsCode({
required String code,
Future<Either<UserModelResponse, Exception>> verifyOtp({
required String email,
required String otp,
required String languageCode,
required Gender gender,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ final class AuthRemoteDataSourceMock implements AuthRemoteDataSource {
Future<void> loginWithEmail(String email) async {}

@override
Future<Either<UserModelResponse, Exception>> fetchSmsCode({
required String code,
Future<Either<UserModelResponse, Exception>> verifyOtp({
required String email,
required String otp,
required String languageCode,
required Gender gender,
}) async {
Expand Down
104 changes: 53 additions & 51 deletions app/lib/app/data/source/remote/auth_remote_data_source_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,55 @@ final class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final SoccialAuth soccialAuth;
final bool isIntegrationTest;

@override
Future<Either<UserModelResponse, Exception>> verifyOtp({
required String otp,
required String email,
required String languageCode,
required Gender gender,
}) async {
try {
final token = await client.postType(
apiConst.loginEmailVerify,
fromJson: TokenResponse.fromJson,
body: {'email': email, 'opt': otp},
);

return token.fold(
(error) {
return Left(error);
},
(r) async {
final user = UserModelResponse(
accessToken: r.key,
username: email,
gender: gender,
localeCode: languageCode,
);

await storage.writeString(key: StorageKeys.tokenKey, value: user.accessToken);

return Right(user);
},
);
} catch (e) {
return Left(Exception('Error fetching SMS code: $e'));
}
}

@override
Future<void> loginWithEmail(String email) async {
try {
await client.postType(
apiConst.loginEmailSend,
fromJson: Map<String, dynamic>.from,
body: {'email': email},
);
} catch (e) {
throw Exception('Error during login: $e');
}
}

@override
Future<Either<UserModelResponse, Exception>> signInWithGoogle(
String languageCode,
Expand Down Expand Up @@ -56,11 +105,11 @@ final class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
);
} else {
final googleAuth = await soccialAuth.signInWithGoogle();
final accessToken = googleAuth.credential?.accessToken ?? '';
final username = googleAuth.user?.displayName ?? '';
final accessToken = googleAuth?['accessToken'] ?? '';
final username = googleAuth?['name'] ?? '';
return _UserReqParam(
name: username,
accessToken: accessToken,
name: username.toString(),
accessToken: accessToken.toString(),
);
}
}
Expand Down Expand Up @@ -149,53 +198,6 @@ final class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
Future<void> logoutRemote() async {
await soccialAuth.logOut();
}

@override
Future<Either<UserModelResponse, Exception>> fetchSmsCode({
required String code,
required String languageCode,
required Gender gender,
}) async {
try {
final token = await client.postType(
'apiConst.fetchSmsCode',
fromJson: TokenResponse.fromJson,
body: {'code': code},
);
return token.fold(
(error) {
return Left(error);
},
(r) async {
final user = UserModelResponse(
accessToken: r.key,
username: '',
gender: gender,
localeCode: languageCode,
);

await storage.writeString(key: StorageKeys.tokenKey, value: user.accessToken);

return Right(user);
},
);
} catch (e) {
return Left(Exception('Error fetching SMS code: $e'));
}
}

@override
Future<void> loginWithEmail(String email) async {
try {
await client.postType(
'apiConst.loginWithEmail',
fromJson: TokenResponse.fromJson,
body: {'email': email},
);
} catch (e) {
throw Exception('Error during login: $e');
}
}
}

@immutable
Expand Down
2 changes: 2 additions & 0 deletions app/lib/app/domain/domain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export 'usecase/ser_user_data_user_case.dart';
export 'usecase/patch_gender_user_case.dart';
export 'usecase/patch_locale_code_use_case.dart';
export 'usecase/logout_use_case.dart';
export 'usecase/email_login_usecase.dart';
export 'usecase/verify_otp_use_case.dart';
export 'entity/user_entity.dart';
export 'entity/user_data_entity.dart';
8 changes: 8 additions & 0 deletions app/lib/app/domain/repository/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import 'package:my_quran/app/app.dart';
abstract class AuthRepository {
UserEntity? get init;

Future<void> loginWithEmail(String email);

Future<Either<UserEntity, Exception>> verifyOtp({
required String email,
required String otp,
required String languageCode,
required Gender gender,
});
Future<Either<UserEntity, Exception>> signWithGoogle(
String languageCode,
Gender gender,
Expand Down
13 changes: 13 additions & 0 deletions app/lib/app/domain/usecase/email_login_usecase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:meta/meta.dart';
import 'package:my_quran/app/app.dart';

@immutable
final class EmailLoginUseCase {
const EmailLoginUseCase(this.repository);

final AuthRepository repository;

Future<void> call(String email) {
return repository.loginWithEmail(email);
}
}
24 changes: 24 additions & 0 deletions app/lib/app/domain/usecase/verify_otp_use_case.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:meta/meta.dart';
import 'package:mq_either/mq_either.dart';
import 'package:my_quran/app/app.dart';

@immutable
final class VerifyOtpUseCase {
const VerifyOtpUseCase(this.repository);

final AuthRepository repository;

Future<Either<UserEntity, Exception>> call({
required String email,
required String otp,
required String languageCode,
required Gender gender,
}) {
return repository.verifyOtp(
email: email,
otp: otp,
languageCode: languageCode,
gender: gender,
);
}
}
26 changes: 26 additions & 0 deletions app/lib/app/presentation/cubit/auth_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class AuthCubit extends Cubit<AuthState> {
this.patchGenderUseCase,
this.patchLocaleCodeUseCase,
this.logoutUseCase,
this.loginWithEmailUsecase,
this.verifyOtpUseCase,
) : super(AuthState(user: getInitialUserUseCase.call));

final GetInitialUserUseCase getInitialUserUseCase;
Expand All @@ -26,6 +28,30 @@ class AuthCubit extends Cubit<AuthState> {
final PatchGenderUseCase patchGenderUseCase;
final PatchLocaleCodeUseCase patchLocaleCodeUseCase;
final LogoutUseCase logoutUseCase;
final EmailLoginUseCase loginWithEmailUsecase;
final VerifyOtpUseCase verifyOtpUseCase;

Future<void> loginWithEmail(String email) async {
try {
await loginWithEmailUsecase(email);
} catch (e) {
emit(state.copyWith(exception: Exception(e)));
}
}

Future<AuthState> verifyOtp(String otp, String email) async {
final user = await verifyOtpUseCase(
email: email,
otp: otp,
languageCode: state.currentLocale.languageCode,
gender: state.gender,
);
user.fold(
(l) => emit(state.copyWith(exception: l)),
(r) => emit(state.copyWith(user: r)),
);
return state;
}

Future<AuthState> signInWithGoogle() async {
final user = await googleSignIn(
Expand Down
2 changes: 2 additions & 0 deletions app/lib/app/presentation/view/app_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class MyApp extends StatelessWidget {
PatchGenderUseCase(context.read<AuthRepository>()),
PatchLocaleCodeUseCase(context.read<AuthRepository>()),
LogoutUseCase(context.read<AuthRepository>()),
EmailLoginUseCase(context.read<AuthRepository>()),
VerifyOtpUseCase(context.read<AuthRepository>()),
),
),
BlocProvider(
Expand Down
1 change: 1 addition & 0 deletions app/lib/components/components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export 'indicators/dot_indicator.dart';
export 'audio/seek_bar.dart';
export 'audio/audio_center_button.dart';
export 'alert/confirmation_dialog.dart';
export 'forms/custom_text_field.dart';
33 changes: 33 additions & 0 deletions app/lib/components/forms/custom_text_field.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';

class CustomTextFormField extends StatelessWidget {
const CustomTextFormField({
this.controller,
this.labelText,
this.obscureText = false,
this.suffixIcon,
this.validator,
super.key,
});

final bool obscureText;
final TextEditingController? controller;
final String? labelText;
final Widget? suffixIcon;
final String? Function(String?)? validator;

@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: labelText,
border: const OutlineInputBorder(),
suffixIcon: suffixIcon,
errorMaxLines: 3,
),
obscureText: obscureText,
validator: validator,
);
}
}
11 changes: 10 additions & 1 deletion app/lib/config/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class AppRouter {
static const hatimRead = 'hatim-read';
static const login = 'login';
static const loginWihtSoccial = 'login-with-soccial';
static const verificationCode = 'verification-code';

static const settingsPage = 'settings';
static const langSettings = 'lang-settings';
Expand All @@ -52,6 +53,14 @@ final class AppRouter {
name: login,
builder: (context, state) => const LoginView(),
),
GoRoute(
path: '/$verificationCode/:email',
name: verificationCode,
builder: (context, state) {
final email = state.pathParameters['email'];
return VerificationCodeView(email: email!);
},
),
GoRoute(
path: '/$loginWihtSoccial',
name: loginWihtSoccial,
Expand Down Expand Up @@ -114,7 +123,7 @@ final class AppRouter {
redirect: (context, state) {
final path = state.matchedLocation;
if (!context.read<AuthCubit>().isAuthedticated) {
if (!path.contains(devModeView) && !path.contains(loginWihtSoccial)) {
if (!path.contains(devModeView) && !path.contains(loginWihtSoccial) && !path.contains(verificationCode)) {
return '/$login';
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/lib/constants/api/api_const.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class ApiConst {
static const domain = 'https://myquran.life';

String get socketBase => 'wss://myquran.life/ws';
String get loginEmailSend => '$_getDomain/api/v1/accounts/otp/send/';
String get loginEmailVerify => '$_getDomain/api/v1/accounts/otp/verify/';
String get loginWithGoogle => '$_getDomain/api/v1/accounts/google/';
String get loginWithApple => '$_getDomain/api/v1/accounts/apple/';
String putProfile(String userId) => '$_getDomain/api/v1/accounts/profile/$userId/';
Expand Down
Loading

0 comments on commit a3cbe44

Please sign in to comment.