diff --git a/assets/locale/de.json b/assets/locale/de.json index 1e99161..43e0b88 100644 --- a/assets/locale/de.json +++ b/assets/locale/de.json @@ -19,5 +19,7 @@ "navBarItemHome": "Startseite", "navBarItemSearch": "Suche", "navBarItemMyCourse": "Meine Kurse", - "navBarItemProfile": "Profil" + "navBarItemProfile": "Profil", + "searchBarPlaceholderText": "Nach Fähigkeiten oder Technologiestapeln suchen", + "homeContinueLearningText": "Lernen fortsetzen" } \ No newline at end of file diff --git a/assets/locale/en.json b/assets/locale/en.json index 9d1d871..25f6c95 100644 --- a/assets/locale/en.json +++ b/assets/locale/en.json @@ -19,5 +19,7 @@ "navBarItemHome": "Home", "navBarItemSearch": "Search", "navBarItemMyCourse": "MyCourse", - "navBarItemProfile": "Profile" + "navBarItemProfile": "Profile", + "searchBarPlaceholderText": "Search by skills or tech stacks", + "homeContinueLearningText": "Continue Learning" } \ No newline at end of file diff --git a/assets/locale/id.json b/assets/locale/id.json index 9a79203..0fb78fb 100644 --- a/assets/locale/id.json +++ b/assets/locale/id.json @@ -19,5 +19,7 @@ "navBarItemHome": "Beranda", "navBarItemSearch": "Cari", "navBarItemMyCourse": "Kursus Saya", - "navBarItemProfile": "Profil" + "navBarItemProfile": "Profil", + "searchBarPlaceholderText": "Cari berdasarkan keahlian atau techstacks", + "homeContinueLearningText": "Lanjutkan Belajar" } \ No newline at end of file diff --git a/assets/locale/ja.json b/assets/locale/ja.json index 248698f..614d9ba 100644 --- a/assets/locale/ja.json +++ b/assets/locale/ja.json @@ -19,5 +19,7 @@ "navBarItemHome": "ホーム", "navBarItemSearch": "検索", "navBarItemMyCourse": "マイコース", - "navBarItemProfile": "プロフィール" + "navBarItemProfile": "プロフィール", + "searchBarPlaceholderText": "スキルや技術スタックで検索", + "homeContinueLearningText": "学習を続ける" } \ No newline at end of file diff --git a/modules/app/lib/presentation/screens/main_screen.dart b/modules/app/lib/presentation/screens/main_screen.dart index 13aff5a..c7c248b 100644 --- a/modules/app/lib/presentation/screens/main_screen.dart +++ b/modules/app/lib/presentation/screens/main_screen.dart @@ -24,12 +24,6 @@ class _MainScreenState extends State { return const HomeScreen(); } - Widget _buildSearchScreen() { - return const Center( - child: Text("Search Screen"), - ); - } - Widget _buildMyCourseScreen() { return const Center( child: Text("My Course Screen"), @@ -47,10 +41,8 @@ class _MainScreenState extends State { case 0: return _buildHomeScreen(); case 1: - return _buildSearchScreen(); - case 2: return _buildMyCourseScreen(); - case 3: + case 2: return _buildProfileScreen(); } return null; @@ -79,15 +71,6 @@ class _MainScreenState extends State { ), label: 'navBarItemHome'.tr(), ), - BottomNavigationBarItem( - icon: Container( - padding: EdgeInsets.only(top: 6.h), - child: const Icon( - Icons.explore_outlined, - ), - ), - label: 'navBarItemSearch'.tr(), - ), BottomNavigationBarItem( icon: Container( padding: EdgeInsets.only(top: 6.h), diff --git a/modules/app/test/screens/main_screen_test.dart b/modules/app/test/screens/main_screen_test.dart index 5feca42..eb29946 100644 --- a/modules/app/test/screens/main_screen_test.dart +++ b/modules/app/test/screens/main_screen_test.dart @@ -17,21 +17,6 @@ void main() { expect(find.byType(HomeScreen), findsOneWidget); }); - testWidgets('Switching to search screen via bottom navigation bar', - (WidgetTester tester) async { - await tester.pumpWidget( - const LocalizationTestApp( - child: MainScreen(), - ), - ); - - await tester.pumpAndSettle(); - - await tester.tap(find.byIcon(Icons.explore_outlined)); - await tester.pumpAndSettle(); - expect(find.text("Search Screen"), findsOneWidget); - }); - testWidgets('Switching to my course screen via bottom navigation bar', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/modules/auth/lib/auth.dart b/modules/auth/lib/auth.dart index c9e3985..46ef9a8 100644 --- a/modules/auth/lib/auth.dart +++ b/modules/auth/lib/auth.dart @@ -2,3 +2,4 @@ library auth; export 'presentation/index.dart'; export 'data/data.dart'; +export 'bloc/bloc.dart'; diff --git a/modules/auth/lib/bloc/sign_in/sign_in_bloc.dart b/modules/auth/lib/bloc/auth/auth_bloc.dart similarity index 53% rename from modules/auth/lib/bloc/sign_in/sign_in_bloc.dart rename to modules/auth/lib/bloc/auth/auth_bloc.dart index e347fe4..55086ed 100644 --- a/modules/auth/lib/bloc/sign_in/sign_in_bloc.dart +++ b/modules/auth/lib/bloc/auth/auth_bloc.dart @@ -1,27 +1,68 @@ -import 'package:auth/data/dto/auth/sign_in_dto.dart'; -import 'package:auth/data/dto/user/user_initialization_dto.dart'; -import 'package:auth/data/remote/auth_services.dart'; -import 'package:auth/data/remote/user_services.dart'; +import 'package:auth/auth.dart'; +import 'package:cwa_core/core.dart'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; +part 'auth_event.dart'; +part 'auth_state.dart'; -part 'sign_in_event.dart'; -part 'sign_in_state.dart'; - -class SignInBloc extends Bloc { +class AuthBloc extends Bloc { final AuthService services; final UserService userService; - - SignInBloc({ + AuthBloc({ required this.services, required this.userService, - }) : super(SignInInitial()) { + }) : super(AuthInitial()) { + // Sign Up Events + on(onSignUp); + on(onSignUpByGoogle); + // Sign In Events on(onSignIn); on(onForgotPassword); on(onSignInByGoogle); } + Future onSignUp(SignUpRequest event, emit) async { + try { + emit(SignUpLoading()); + + final user = await services.signUp(event.dto); + await userService.initializeUser(UserInitializationDTO( + id: user.uid, + name: event.dto.name, + email: event.dto.email, + profilePicture: user.photoURL ?? getAvatarUrl(event.dto.name), + )); + + emit(SignUpSuccess()); + } catch (e) { + emit(SignUpFailed(error: e.toString())); + } + } + + Future onSignUpByGoogle(SignUpByGoogleRequest event, emit) async { + try { + emit(SignUpLoading()); + + final user = await services.signInWithGoogle(); + await userService.initializeUser(UserInitializationDTO( + id: user.uid, + name: user.displayName!, + email: user.email!, + profilePicture: user.photoURL!, + )); + + emit(SignUpSuccess()); + } catch (e) { + emit( + SignUpFailed( + error: e.toString(), + ), + ); + } + } + Future onSignIn(SignInRequest event, emit) async { try { emit(SignInLoading()); diff --git a/modules/auth/lib/bloc/auth/auth_event.dart b/modules/auth/lib/bloc/auth/auth_event.dart new file mode 100644 index 0000000..973a770 --- /dev/null +++ b/modules/auth/lib/bloc/auth/auth_event.dart @@ -0,0 +1,39 @@ +part of 'auth_bloc.dart'; + +sealed class AuthEvent extends Equatable { + const AuthEvent(); + + @override + List get props => []; +} + +class SignUpRequest extends AuthEvent { + final SignUpDTO dto; + + const SignUpRequest({required this.dto}); + + @override + List get props => [dto]; +} + +class SignUpByGoogleRequest extends AuthEvent {} + +class SignInRequest extends AuthEvent { + final SignInDTO signInDTO; + + const SignInRequest({required this.signInDTO}); + + @override + List get props => [signInDTO]; +} + +class ForgotPasswordRequest extends AuthEvent { + final String email; + + const ForgotPasswordRequest({required this.email}); + + @override + List get props => [email]; +} + +class SignInByGoogleRequest extends AuthEvent {} diff --git a/modules/auth/lib/bloc/auth/auth_state.dart b/modules/auth/lib/bloc/auth/auth_state.dart new file mode 100644 index 0000000..54e2a95 --- /dev/null +++ b/modules/auth/lib/bloc/auth/auth_state.dart @@ -0,0 +1,51 @@ +part of 'auth_bloc.dart'; + +sealed class AuthState extends Equatable { + const AuthState(); + + @override + List get props => []; +} + +final class AuthInitial extends AuthState {} + +// Sign Up States +final class SignUpLoading extends AuthState {} + +final class SignUpFailed extends AuthState { + final String error; + + const SignUpFailed({required this.error}); + + @override + List get props => [error]; +} + +// Sign In States +final class SignUpSuccess extends AuthState {} + +final class SignInLoading extends AuthState {} + +final class SignInFailed extends AuthState { + final String error; + + const SignInFailed({required this.error}); + + @override + List get props => [error]; +} + +final class SignInSuccess extends AuthState {} + +final class ForgotPasswordLoading extends AuthState {} + +final class ForgotPasswordFailed extends AuthState { + final String error; + + const ForgotPasswordFailed({required this.error}); + + @override + List get props => [error]; +} + +final class ForgotPasswordSuccess extends AuthState {} diff --git a/modules/auth/lib/bloc/bloc.dart b/modules/auth/lib/bloc/bloc.dart new file mode 100644 index 0000000..c89ef94 --- /dev/null +++ b/modules/auth/lib/bloc/bloc.dart @@ -0,0 +1 @@ +export 'auth/auth_bloc.dart'; \ No newline at end of file diff --git a/modules/auth/lib/bloc/sign_in/sign_in_event.dart b/modules/auth/lib/bloc/sign_in/sign_in_event.dart deleted file mode 100644 index 7862347..0000000 --- a/modules/auth/lib/bloc/sign_in/sign_in_event.dart +++ /dev/null @@ -1,28 +0,0 @@ -part of 'sign_in_bloc.dart'; - -sealed class SignInEvent extends Equatable { - const SignInEvent(); - - @override - List get props => []; -} - -class SignInRequest extends SignInEvent { - final SignInDTO signInDTO; - - const SignInRequest({required this.signInDTO}); - - @override - List get props => [signInDTO]; -} - -class ForgotPasswordRequest extends SignInEvent { - final String email; - - const ForgotPasswordRequest({required this.email}); - - @override - List get props => [email]; -} - -class SignInByGoogleRequest extends SignInEvent {} diff --git a/modules/auth/lib/bloc/sign_in/sign_in_state.dart b/modules/auth/lib/bloc/sign_in/sign_in_state.dart deleted file mode 100644 index 73c21d4..0000000 --- a/modules/auth/lib/bloc/sign_in/sign_in_state.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of 'sign_in_bloc.dart'; - -sealed class SignInState extends Equatable { - const SignInState(); - - @override - List get props => []; -} - -final class SignInInitial extends SignInState {} - -final class SignInLoading extends SignInState {} - -final class SignInFailed extends SignInState { - final String error; - - const SignInFailed({required this.error}); -} - -final class SignInSuccess extends SignInState {} - -final class ForgotPasswordLoading extends SignInState {} - -final class ForgotPasswordFailed extends SignInState { - final String error; - - const ForgotPasswordFailed({required this.error}); -} - -final class ForgotPasswordSuccess extends SignInState {} diff --git a/modules/auth/lib/bloc/sign_up/sign_up_bloc.dart b/modules/auth/lib/bloc/sign_up/sign_up_bloc.dart deleted file mode 100644 index 36918e0..0000000 --- a/modules/auth/lib/bloc/sign_up/sign_up_bloc.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:auth/data/dto/auth/sign_up_dto.dart'; -import 'package:auth/data/dto/user/user_initialization_dto.dart'; -import 'package:auth/data/remote/auth_services.dart'; -import 'package:auth/data/remote/user_services.dart'; -import 'package:cwa_core/core.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:equatable/equatable.dart'; - -part 'sign_up_event.dart'; -part 'sign_up_state.dart'; - -class SignUpBloc extends Bloc { - final AuthService services; - final UserService userService; - - SignUpBloc({ - required this.services, - required this.userService, - }) : super(SignUpInitial()) { - on(onSignUp); - on(onSignUpByGoogle); - } - - Future onSignUp(SignUpRequest event, emit) async { - try { - emit(SignUpLoading()); - - final user = await services.signUp(event.dto); - await userService.initializeUser(UserInitializationDTO( - id: user.uid, - name: event.dto.name, - email: event.dto.email, - profilePicture: user.photoURL ?? getAvatarUrl(event.dto.name), - )); - - emit(SignUpSuccess()); - } catch (e) { - emit(SignUpFailed(error: e.toString())); - } - } - - Future onSignUpByGoogle(SignUpByGoogleRequest event, emit) async { - try { - emit(SignUpLoading()); - - final user = await services.signInWithGoogle(); - await userService.initializeUser(UserInitializationDTO( - id: user.uid, - name: user.displayName!, - email: user.email!, - profilePicture: user.photoURL!, - )); - - emit(SignUpSuccess()); - } catch (e) { - emit( - SignUpFailed( - error: e.toString(), - ), - ); - } - } -} diff --git a/modules/auth/lib/bloc/sign_up/sign_up_event.dart b/modules/auth/lib/bloc/sign_up/sign_up_event.dart deleted file mode 100644 index bae703a..0000000 --- a/modules/auth/lib/bloc/sign_up/sign_up_event.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of 'sign_up_bloc.dart'; - -sealed class SignUpEvent extends Equatable { - const SignUpEvent(); - - @override - List get props => []; -} - -class SignUpRequest extends SignUpEvent { - final SignUpDTO dto; - - const SignUpRequest({required this.dto}); - - @override - List get props => [dto]; -} - -class SignUpByGoogleRequest extends SignUpEvent {} diff --git a/modules/auth/lib/bloc/sign_up/sign_up_state.dart b/modules/auth/lib/bloc/sign_up/sign_up_state.dart deleted file mode 100644 index ec40319..0000000 --- a/modules/auth/lib/bloc/sign_up/sign_up_state.dart +++ /dev/null @@ -1,23 +0,0 @@ -part of 'sign_up_bloc.dart'; - -sealed class SignUpState extends Equatable { - const SignUpState(); - - @override - List get props => []; -} - -final class SignUpInitial extends SignUpState {} - -final class SignUpLoading extends SignUpState {} - -final class SignUpFailed extends SignUpState { - final String error; - - const SignUpFailed({required this.error}); - - // @override - // List get props => [error]; -} - -final class SignUpSuccess extends SignUpState {} diff --git a/modules/auth/lib/data/data.dart b/modules/auth/lib/data/data.dart index 180c2fc..31eb517 100644 --- a/modules/auth/lib/data/data.dart +++ b/modules/auth/lib/data/data.dart @@ -1,2 +1,3 @@ export "dto/dto.dart"; -export 'remote/services.dart'; \ No newline at end of file +export 'remote/services.dart'; +export 'enum/auth_type_enum.dart'; \ No newline at end of file diff --git a/modules/auth/lib/data/enum/auth_type_enum.dart b/modules/auth/lib/data/enum/auth_type_enum.dart new file mode 100644 index 0000000..8b64c54 --- /dev/null +++ b/modules/auth/lib/data/enum/auth_type_enum.dart @@ -0,0 +1 @@ +enum AuthType { signIn, signUp } diff --git a/modules/auth/lib/presentation/screens/sign_in_screen.dart b/modules/auth/lib/presentation/screens/sign_in_screen.dart index ad75f8a..f6bd588 100644 --- a/modules/auth/lib/presentation/screens/sign_in_screen.dart +++ b/modules/auth/lib/presentation/screens/sign_in_screen.dart @@ -1,6 +1,5 @@ import 'package:app/app.dart'; import 'package:auth/auth.dart'; -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; import 'package:cwa_core/core.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -16,7 +15,7 @@ class SignInScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocListener( + return BlocListener( listener: (context, state) { if (state is SignInFailed) { context.loaderOverlay.hide(); @@ -62,7 +61,7 @@ class SignInScreen extends StatelessWidget { BuildCreateAccountButton(), OrDividerWidget(), OAuthSignInButton( - bloc: SignInBloc, + type: AuthType.signIn, ), ], ), @@ -168,7 +167,7 @@ class BuildSignInFormsState extends State { onTap: () { if (emailController.text.isNotEmpty) { context - .read() + .read() .add(ForgotPasswordRequest(email: emailController.text)); } else { FlushbarUtils.showFlushbar( @@ -205,7 +204,7 @@ class BuildSignInButton extends StatelessWidget { onTap: () { if (emailController.text.isNotEmpty && passwordController.text.isNotEmpty) { - context.read().add(SignInRequest( + context.read().add(SignInRequest( signInDTO: SignInDTO( email: emailController.text, password: passwordController.text))); diff --git a/modules/auth/lib/presentation/screens/sign_up_screen.dart b/modules/auth/lib/presentation/screens/sign_up_screen.dart index 808a665..c2bc654 100644 --- a/modules/auth/lib/presentation/screens/sign_up_screen.dart +++ b/modules/auth/lib/presentation/screens/sign_up_screen.dart @@ -1,6 +1,5 @@ import 'package:app/app.dart'; import 'package:auth/auth.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; import 'package:cwa_core/core.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -13,7 +12,7 @@ class SignUpScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocListener( + return BlocListener( listener: (context, state) { if (state is SignUpFailed) { context.loaderOverlay.hide(); @@ -47,7 +46,7 @@ class SignUpScreen extends StatelessWidget { BuildHaveAccountButton(), OrDividerWidget(), OAuthSignInButton( - bloc: SignUpBloc, + type: AuthType.signUp, ), ], ), @@ -197,7 +196,7 @@ class BuildSignUpButton extends StatelessWidget { password: passwordController.text, ); - context.read().add( + context.read().add( SignUpRequest( dto: dto, ), diff --git a/modules/auth/lib/presentation/widgets/google_sign_in_button.dart b/modules/auth/lib/presentation/widgets/google_sign_in_button.dart index 5e09dad..6315e8b 100644 --- a/modules/auth/lib/presentation/widgets/google_sign_in_button.dart +++ b/modules/auth/lib/presentation/widgets/google_sign_in_button.dart @@ -1,5 +1,5 @@ -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; +import 'package:auth/bloc/auth/auth_bloc.dart'; +import 'package:auth/data/enum/auth_type_enum.dart'; import 'package:cwa_core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -8,8 +8,9 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; class GoogleSignInButton extends StatelessWidget { final EdgeInsets? padding; final EdgeInsets? margin; - final Type? bloc; - const GoogleSignInButton({super.key, this.padding, this.margin, this.bloc}); + final AuthType type; + const GoogleSignInButton( + {super.key, this.padding, this.margin, required this.type}); @override Widget build(BuildContext context) { @@ -18,11 +19,11 @@ class GoogleSignInButton extends StatelessWidget { margin: margin, child: InkWell( onTap: () { - if (bloc == SignInBloc) { - context.read().add(SignInByGoogleRequest()); + if (type == AuthType.signIn) { + context.read().add(SignInByGoogleRequest()); } - if (bloc == SignUpBloc) { - context.read().add(SignUpByGoogleRequest()); + if (type == AuthType.signUp) { + context.read().add(SignUpByGoogleRequest()); } }, child: Image.asset( diff --git a/modules/auth/lib/presentation/widgets/oauth_sign_in_button.dart b/modules/auth/lib/presentation/widgets/oauth_sign_in_button.dart index 765d95d..3c178bd 100644 --- a/modules/auth/lib/presentation/widgets/oauth_sign_in_button.dart +++ b/modules/auth/lib/presentation/widgets/oauth_sign_in_button.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class OAuthSignInButton extends StatelessWidget { - final Type? bloc; - const OAuthSignInButton({super.key, this.bloc}); + final AuthType type; + + const OAuthSignInButton({super.key, required this.type}); @override Widget build(BuildContext context) { @@ -16,7 +17,7 @@ class OAuthSignInButton extends StatelessWidget { } return GoogleSignInButton( - bloc: bloc, + type: type, padding: EdgeInsets.only(top: 30.h), ); } diff --git a/modules/auth/test/bloc/sign_in_bloc_test.dart b/modules/auth/test/bloc/auth_bloc_test.dart similarity index 50% rename from modules/auth/test/bloc/sign_in_bloc_test.dart rename to modules/auth/test/bloc/auth_bloc_test.dart index 6c6b516..1cf2077 100644 --- a/modules/auth/test/bloc/sign_in_bloc_test.dart +++ b/modules/auth/test/bloc/auth_bloc_test.dart @@ -1,5 +1,6 @@ -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; +import 'package:auth/bloc/auth/auth_bloc.dart'; import 'package:auth/data/dto/auth/sign_in_dto.dart'; +import 'package:auth/data/dto/auth/sign_up_dto.dart'; import 'package:auth/data/dto/user/user_initialization_dto.dart'; import 'package:auth/data/remote/auth_services.dart'; import 'package:auth/data/remote/user_services.dart'; @@ -32,7 +33,7 @@ class MockUser extends Mock implements User { } void main() { - late SignInBloc signInBloc; + late AuthBloc authBloc; late AuthService mockAuthService; late UserService mockUserService; late GetIt getIt; @@ -41,6 +42,9 @@ void main() { registerFallbackValue( const SignInDTO(email: "email", password: "password"), ); + registerFallbackValue( + const SignUpDTO(name: "name", email: "email", password: "password"), + ); registerFallbackValue( const UserInitializationDTO( name: "name", @@ -58,18 +62,18 @@ void main() { mockAuthService = getIt(); mockUserService = getIt(); - signInBloc = SignInBloc( + authBloc = AuthBloc( services: mockAuthService, userService: mockUserService, ); }); tearDown(() { - signInBloc.close(); + authBloc.close(); getIt.reset(); }); - group("Sign In Bloc Test", () { + group("Auth Bloc Test Group (Sign In)", () { SignInDTO signInDTO = const SignInDTO( email: "email", password: "password", @@ -92,7 +96,7 @@ void main() { expect(event1, isNot(equals(event2))); }); - blocTest( + blocTest( "emits [SignInLoading, SignInSuccess] when SignInRequest is added successfully", build: () { when(() => mockAuthService.signInWithEmailAndPassword(any())) @@ -100,7 +104,7 @@ void main() { (_) async => MockUser(), ); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(SignInRequest(signInDTO: signInDTO)), expect: () => [ @@ -109,26 +113,26 @@ void main() { ], ); - blocTest( + blocTest( "emits [SignInLoading, SignInFailed] when signInService throws an exception", build: () { when(() => mockAuthService.signInWithEmailAndPassword(any())).thenThrow( - FirebaseException( - plugin: "FirebaseAuthException", + FirebaseAuthException( message: "Failed To Sign In", + code: '401', ), ); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(SignInRequest(signInDTO: signInDTO)), expect: () => [ SignInLoading(), - const SignInFailed(error: "Failed To Sign In"), + const SignInFailed(error: "[firebase_auth/401] Failed To Sign In"), ], ); - blocTest( + blocTest( "emits [SignInLoading, SignInSuccess] when SignInByGoogleRequest is added successfully", build: () { when(() => mockAuthService.signInWithGoogle()).thenAnswer( @@ -137,7 +141,7 @@ void main() { when(() => mockUserService.initializeUser(any())) .thenAnswer((_) async {}); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(SignInByGoogleRequest()), expect: () => [ @@ -146,29 +150,29 @@ void main() { ], ); - blocTest( + blocTest( "emits [SignInLoading, SignInSuccess] when SignInByGoogleRequest throws an exception", build: () { when(() => mockAuthService.signInWithGoogle()).thenThrow( - FirebaseException( - plugin: "FirebaseAuthException", + FirebaseAuthException( message: "Failed To Sign In", + code: '401', ), ); when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { return; }); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(SignInByGoogleRequest()), expect: () => [ SignInLoading(), - const SignInFailed(error: "Failed To Sign In"), + const SignInFailed(error: "[firebase_auth/401] Failed To Sign In"), ], ); - blocTest( + blocTest( "emits [SignInLoading, SignInSuccess] when SignInByGoogleRequest userinitialization throws an exception", build: () { when(() => mockAuthService.signInWithGoogle()).thenAnswer( @@ -177,7 +181,7 @@ void main() { when(() => mockUserService.initializeUser(any())) .thenThrow("Internal Server Error"); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(SignInByGoogleRequest()), expect: () => [ @@ -186,7 +190,7 @@ void main() { ], ); - blocTest( + blocTest( "emits [ForgotPasswordLoading,ForgotPasswordSuccess] when ForgotPasswordRequest is added successfully", build: () { when(() => mockAuthService.sendPasswordResetEmail(any())) @@ -194,7 +198,7 @@ void main() { return; }); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(const ForgotPasswordRequest(email: "email")), expect: () => [ @@ -203,25 +207,157 @@ void main() { ], ); - blocTest( + blocTest( "emits [ForgotPasswordLoading,ForgotPasswordFailed] when ForgotPasswordRequest is failed", build: () { when(() => mockAuthService.sendPasswordResetEmail(any())).thenThrow( - FirebaseException( - plugin: "FirebaseAuthException", + FirebaseAuthException( + code: "401", message: "Failed To Send Reset Password Email", ), ); - return signInBloc; + return authBloc; }, act: (bloc) => bloc.add(const ForgotPasswordRequest(email: "email")), expect: () => [ ForgotPasswordLoading(), const ForgotPasswordFailed( - error: "Failed To Send Reset Password Email", + error: "[firebase_auth/401] Failed To Send Reset Password Email", ), ], ); }); + + group("Auth Bloc Test Group (Sign Up)", () { + SignUpDTO signUpDTO = + const SignUpDTO(name: "name", email: "email", password: "password"); + + test("SignUpEvent instances with same type are equal", () { + const event1 = SignUpRequest( + dto: SignUpDTO(name: "name", email: "email", password: "password")); + const event2 = SignUpRequest( + dto: SignUpDTO(name: "name", email: "email", password: "password")); + + expect(event1, equals(event2)); + }); + + test("SignUpEvent instances with same type are not equal", () { + const event1 = SignUpRequest( + dto: SignUpDTO(name: "name", email: "email", password: "password")); + final event2 = SignUpByGoogleRequest(); + + expect(event1, isNot(equals(event2))); + }); + + blocTest( + "emits [SignUpLoading, SignUpSuccess] when SignUpRequest is added successfully", + build: () { + when(() => mockAuthService.signUp(any())) + .thenAnswer((_) async => MockUser()); + when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { + return; + }); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), + expect: () => [ + SignUpLoading(), + SignUpSuccess(), + ], + ); + + blocTest( + "emits [SignUpLoading, SignUpFailed] when signupService throws an exception", + build: () { + when(() => mockAuthService.signUp(any())).thenThrow( + FirebaseAuthException( + code: "401", + message: "Failed To Sign Up", + ), + ); + when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { + return; + }); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), + expect: () => [ + SignUpLoading(), + const SignUpFailed( + error: "[firebase_auth/401] Failed To Sign Up", + ), + ], + ); + + blocTest( + "emits [SignUpLoading, SignUpFailed] when Initialize User Service throws an exception", + build: () { + when(() => mockAuthService.signUp(any())) + .thenAnswer((_) async => MockUser()); + when(() => mockUserService.initializeUser(any())) + .thenThrow("Internal Server Error"); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), + expect: () => [ + SignUpLoading(), + const SignUpFailed(error: "Internal Server Error"), + ], + ); + + blocTest( + "emits [SignUpLoading, SignUpSuccess] when SignUpByGoogleRequest is added successfully", + build: () { + when(() => mockAuthService.signInWithGoogle()) + .thenAnswer((_) async => MockUser()); + when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { + return; + }); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpByGoogleRequest()), + expect: () => [ + SignUpLoading(), + SignUpSuccess(), + ], + ); + + blocTest( + "emits [SignUpLoading, SignUpFailed] when signInWithGoogle is Throw Error", + build: () { + when(() => mockAuthService.signInWithGoogle()).thenThrow( + FirebaseAuthException( + code: "401", + message: "Failed To Sign Up", + ), + ); + when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { + return; + }); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpByGoogleRequest()), + expect: () => [ + SignUpLoading(), + const SignUpFailed(error: "[firebase_auth/401] Failed To Sign Up"), + ], + ); + + blocTest( + "emits [SignUpLoading, SignUpFailed] SignUpWithGoogle when Initialize User Service is Throw Error", + build: () { + when(() => mockAuthService.signInWithGoogle()) + .thenAnswer((_) async => MockUser()); + when(() => mockUserService.initializeUser(any())) + .thenThrow("Internal Server Error"); + return authBloc; + }, + act: (bloc) => bloc.add(SignUpByGoogleRequest()), + expect: () => [ + SignUpLoading(), + const SignUpFailed(error: "Internal Server Error"), + ], + ); + }); } diff --git a/modules/auth/test/bloc/sign_up_bloc_test.dart b/modules/auth/test/bloc/sign_up_bloc_test.dart deleted file mode 100644 index 3fe103a..0000000 --- a/modules/auth/test/bloc/sign_up_bloc_test.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; -import 'package:auth/data/dto/auth/sign_up_dto.dart'; -import 'package:auth/data/dto/user/user_initialization_dto.dart'; -import 'package:auth/data/remote/auth_services.dart'; -import 'package:auth/data/remote/user_services.dart'; -import 'package:bloc_test/bloc_test.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:get_it/get_it.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockAuthService extends Mock implements AuthService {} - -class MockUserService extends Mock implements UserService {} - -class MockUser extends Mock implements User { - @override - final String uid; - @override - final String displayName; - @override - final String email; - @override - final String photoURL; - - MockUser({ - this.uid = 'id', - this.displayName = 'name', - this.email = 'email', - this.photoURL = 'url', - }); -} - -void main() { - late SignUpBloc signUpBloc; - late AuthService mockAuthService; - late UserService mockUserService; - late GetIt getIt; - - setUpAll(() { - registerFallbackValue( - const SignUpDTO(name: "name", email: "email", password: "password"), - ); - registerFallbackValue( - const UserInitializationDTO( - name: "name", - email: "email", - id: "id", - profilePicture: "example.com"), - ); - }); - - setUp(() { - getIt = GetIt.instance; - getIt.registerSingleton(MockAuthService()); - getIt.registerSingleton(MockUserService()); - - mockAuthService = getIt(); - mockUserService = getIt(); - - signUpBloc = SignUpBloc( - services: mockAuthService, - userService: mockUserService, - ); - }); - - tearDown(() { - signUpBloc.close(); - getIt.reset(); - }); - - group("Sign Up Bloc Test", () { - SignUpDTO signUpDTO = - const SignUpDTO(name: "name", email: "email", password: "password"); - - test("SignUpEvent instances with same type are equal", () { - const event1 = SignUpRequest( - dto: SignUpDTO(name: "name", email: "email", password: "password")); - const event2 = SignUpRequest( - dto: SignUpDTO(name: "name", email: "email", password: "password")); - - expect(event1, equals(event2)); - }); - - test("SignUpEvent instances with same type are not equal", () { - const event1 = SignUpRequest( - dto: SignUpDTO(name: "name", email: "email", password: "password")); - final event2 = SignUpByGoogleRequest(); - - expect(event1, isNot(equals(event2))); - }); - - blocTest( - "emits [SignUpLoading, SignUpSuccess] when SignUpRequest is added successfully", - build: () { - when(() => mockAuthService.signUp(any())) - .thenAnswer((_) async => MockUser()); - when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { - return; - }); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), - expect: () => [ - SignUpLoading(), - SignUpSuccess(), - ], - ); - - blocTest( - "emits [SignUpLoading, SignUpFailed] when signupService throws an exception", - build: () { - when(() => mockAuthService.signUp(any())).thenThrow( - FirebaseException( - plugin: "FirebaseAuthException", - message: "Failed To Sign Up", - ), - ); - when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { - return; - }); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), - expect: () => [ - SignUpLoading(), - const SignUpFailed(error: "Failed To Sign Up"), - ], - ); - - blocTest( - "emits [SignUpLoading, SignUpFailed] when Initialize User Service throws an exception", - build: () { - when(() => mockAuthService.signUp(any())) - .thenAnswer((_) async => MockUser()); - when(() => mockUserService.initializeUser(any())) - .thenThrow("Internal Server Error"); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpRequest(dto: signUpDTO)), - expect: () => [ - SignUpLoading(), - const SignUpFailed(error: "Internal Server Error"), - ], - ); - - blocTest( - "emits [SignUpLoading, SignUpSuccess] when SignUpByGoogleRequest is added successfully", - build: () { - when(() => mockAuthService.signInWithGoogle()) - .thenAnswer((_) async => MockUser()); - when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { - return; - }); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpByGoogleRequest()), - expect: () => [ - SignUpLoading(), - SignUpSuccess(), - ], - ); - - blocTest( - "emits [SignUpLoading, SignUpFailed] when signInWithGoogle is Throw Error", - build: () { - when(() => mockAuthService.signInWithGoogle()).thenThrow( - FirebaseException( - plugin: "FirebaseAuthException", - message: "Failed To Sign Up", - ), - ); - when(() => mockUserService.initializeUser(any())).thenAnswer((_) async { - return; - }); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpByGoogleRequest()), - expect: () => [ - SignUpLoading(), - const SignUpFailed(error: "Failed To Sign Up"), - ], - ); - - blocTest( - "emits [SignUpLoading, SignUpFailed] SignUpWithGoogle when Initialize User Service is Throw Error", - build: () { - when(() => mockAuthService.signInWithGoogle()) - .thenAnswer((_) async => MockUser()); - when(() => mockUserService.initializeUser(any())) - .thenThrow("Internal Server Error"); - return signUpBloc; - }, - act: (bloc) => bloc.add(SignUpByGoogleRequest()), - expect: () => [ - SignUpLoading(), - const SignUpFailed(error: "Internal Server Error"), - ], - ); - }); -} diff --git a/modules/auth/test/screens/sign_in_screen_test.dart b/modules/auth/test/screens/sign_in_screen_test.dart index c12c340..5363015 100644 --- a/modules/auth/test/screens/sign_in_screen_test.dart +++ b/modules/auth/test/screens/sign_in_screen_test.dart @@ -1,7 +1,6 @@ import 'package:app/presentation/widgets/custom_text_button.dart'; import 'package:app/presentation/widgets/custom_textform_field.dart'; import 'package:auth/auth.dart'; -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:cwa_core/test_helper/test.dart'; import 'package:firebase_auth/firebase_auth.dart'; @@ -11,7 +10,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:mocktail/mocktail.dart'; - enum NavigatorAction { push, pop, replaced } class MockNavigatorObserver extends NavigatorObserver { @@ -45,7 +43,7 @@ class MockUser extends Mock implements User { MockUser({this.uid = 'id', this.displayName = 'name', this.email = 'email'}); } -class MockSignInBloc extends Mock implements SignInBloc { +class MockSignInBloc extends Mock implements AuthBloc { @override final AuthService services; @override @@ -57,7 +55,7 @@ class MockSignInBloc extends Mock implements SignInBloc { } void main() { - late SignInBloc signInBloc; + late AuthBloc authBloc; late AuthService mockAuthService; late UserService mockUserService; late GetIt getIt; @@ -84,14 +82,14 @@ void main() { mockAuthService = getIt(); mockUserService = getIt(); - signInBloc = MockSignInBloc( + authBloc = MockSignInBloc( services: mockAuthService, userService: mockUserService, ); }); tearDown(() { - signInBloc.close(); + authBloc.close(); getIt.reset(); }); group('SignInScreen Widgets Test', () { @@ -147,19 +145,19 @@ void main() { testWidgets('BuildSignInForms Widget Test ForgotPassword Success', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), ForgotPasswordLoading(), ForgotPasswordSuccess(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( TestApp( - home: BlocProvider( - create: (context) => signInBloc, + home: BlocProvider( + create: (context) => authBloc, child: const Material( child: BuildSignInForms(), ), @@ -178,7 +176,7 @@ void main() { await tester.tap(find.text('forgotPasswordText'), warnIfMissed: false); verify( - () => signInBloc.add( + () => authBloc.add( const ForgotPasswordRequest(email: 'email@domain.com'), ), ).called(1); @@ -189,19 +187,19 @@ void main() { testWidgets('BuildSignInForms Widget Test ForgotPassword Failed', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), ForgotPasswordLoading(), const ForgotPasswordFailed(error: "Please emailHintText"), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( TestApp( home: BlocProvider( - create: (context) => signInBloc, + create: (context) => authBloc, child: const Material( child: BuildSignInForms(), ), @@ -218,19 +216,19 @@ void main() { testWidgets('BuildSignInButton Widget Test', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), SignInLoading(), SignInSuccess(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( LocalizationTestApp( - child: BlocProvider( - create: (context) => signInBloc, + child: BlocProvider( + create: (context) => authBloc, child: Builder( builder: (context) { return Material( @@ -255,7 +253,7 @@ void main() { await tester.tap(find.byType(InkWell), warnIfMissed: false); verify( - () => signInBloc.add( + () => authBloc.add( const SignInRequest( signInDTO: SignInDTO(email: "email@domain.com", password: "password"), @@ -267,17 +265,17 @@ void main() { testWidgets('BuildSignInButton Widget Test Empty Forms', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( LocalizationTestApp( - child: BlocProvider( - create: (context) => signInBloc, + child: BlocProvider( + create: (context) => authBloc, child: Builder( builder: (context) { return Material( @@ -345,11 +343,11 @@ void main() { testWidgets('SignInScreen Widget Test', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( @@ -357,8 +355,8 @@ void main() { home: Material( child: MediaQuery( data: const MediaQueryData(textScaler: TextScaler.linear(0.5)), - child: BlocProvider.value( - value: signInBloc, + child: BlocProvider.value( + value: authBloc, child: SignInScreen(), ), ), @@ -371,18 +369,18 @@ void main() { testWidgets('SignInBlocListener Test', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), SignInLoading(), SignInSuccess(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider( - create: (context) => signInBloc, + BlocProvider( + create: (context) => authBloc, child: TestApp( routes: { '/': (_) => MediaQuery( @@ -402,7 +400,7 @@ void main() { await tester.pumpAndSettle(); - signInBloc.add( + authBloc.add( const SignInRequest( signInDTO: SignInDTO( email: "email", @@ -415,18 +413,18 @@ void main() { testWidgets('SignInBlocListener Test Failed Test', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), SignInLoading(), const SignInFailed(error: "Failed to Sign In"), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider( - create: (context) => signInBloc, + BlocProvider( + create: (context) => authBloc, child: TestApp( routes: { '/': (_) => MediaQuery( @@ -446,7 +444,7 @@ void main() { await tester.pumpAndSettle(); - signInBloc.add( + authBloc.add( const SignInRequest( signInDTO: SignInDTO( email: "email", @@ -463,18 +461,15 @@ void main() { testWidgets('SignInBlocListener Test Failed Forgot Success', (WidgetTester tester) async { whenListen( - signInBloc, - Stream.fromIterable([ - SignInInitial(), - ForgotPasswordLoading(), - ForgotPasswordSuccess() - ]), - initialState: SignInInitial(), + authBloc, + Stream.fromIterable( + [AuthInitial(), ForgotPasswordLoading(), ForgotPasswordSuccess()]), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider( - create: (context) => signInBloc, + BlocProvider( + create: (context) => authBloc, child: TestApp( routes: { '/': (_) => MediaQuery( @@ -494,7 +489,7 @@ void main() { await tester.pumpAndSettle(); - signInBloc.add( + authBloc.add( const ForgotPasswordRequest( email: "email", ), @@ -508,18 +503,18 @@ void main() { testWidgets('SignInBlocListener Test Failed Forgot Password', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), ForgotPasswordLoading(), const ForgotPasswordFailed(error: "Failed to Send Email"), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider( - create: (context) => signInBloc, + BlocProvider( + create: (context) => authBloc, child: TestApp( routes: { '/': (_) => MediaQuery( @@ -539,7 +534,7 @@ void main() { await tester.pumpAndSettle(); - signInBloc.add( + authBloc.add( const ForgotPasswordRequest( email: "email", ), diff --git a/modules/auth/test/screens/sign_up_screen_test.dart b/modules/auth/test/screens/sign_up_screen_test.dart index 39a5e40..0d629bb 100644 --- a/modules/auth/test/screens/sign_up_screen_test.dart +++ b/modules/auth/test/screens/sign_up_screen_test.dart @@ -1,6 +1,5 @@ import 'package:app/presentation/widgets/custom_textform_field.dart'; import 'package:auth/auth.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:cwa_core/test_helper/test.dart'; @@ -11,7 +10,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:mocktail/mocktail.dart'; - enum NavigatorAction { push, pop, replaced } class MockNavigatorObserver extends NavigatorObserver { @@ -45,19 +43,19 @@ class MockUser extends Mock implements User { MockUser({this.uid = 'id', this.displayName = 'name', this.email = 'email'}); } -class MockSignUpBloc extends Mock implements SignUpBloc { +class MockAuthBloc extends Mock implements AuthBloc { @override final AuthService services; @override final UserService userService; - MockSignUpBloc({required this.services, required this.userService}) { + MockAuthBloc({required this.services, required this.userService}) { when(() => close()).thenAnswer((_) async => {}); } } void main() { - late SignUpBloc signUpBloc; + late AuthBloc authBloc; late AuthService mockAuthService; late UserService mockUserService; late GetIt getIt; @@ -84,14 +82,14 @@ void main() { mockAuthService = getIt(); mockUserService = getIt(); - signUpBloc = MockSignUpBloc( + authBloc = MockAuthBloc( services: mockAuthService, userService: mockUserService, ); }); tearDown(() { - signUpBloc.close(); + authBloc.close(); getIt.reset(); }); @@ -164,19 +162,19 @@ void main() { TextEditingController(text: "Password"); whenListen( - signUpBloc, + authBloc, Stream.fromIterable([ - SignUpInitial(), + AuthInitial(), SignUpLoading(), SignUpSuccess(), ]), - initialState: SignUpInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( LocalizationTestApp( - child: BlocProvider.value( - value: signUpBloc, + child: BlocProvider.value( + value: authBloc, child: Builder(builder: (context) { return Material( child: BuildSignUpButton( @@ -200,7 +198,7 @@ void main() { await tester.pumpAndSettle(); verify( - () => signUpBloc.add( + () => authBloc.add( const SignUpRequest( dto: SignUpDTO( name: "Name", @@ -223,8 +221,8 @@ void main() { await tester.pumpWidget( LocalizationTestApp( - child: BlocProvider( - create: (context) => signUpBloc, + child: BlocProvider( + create: (context) => authBloc, child: Builder(builder: (context) { return Material( child: BuildSignUpButton( @@ -297,18 +295,18 @@ void main() { testWidgets('SignUp BlocListener Test', (WidgetTester tester) async { whenListen( - signUpBloc, + authBloc, Stream.fromIterable([ - SignUpInitial(), + AuthInitial(), SignUpLoading(), SignUpSuccess(), ]), - initialState: SignUpInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider.value( - value: signUpBloc, + BlocProvider.value( + value: authBloc, child: TestApp( routes: { '/': (_) => const MediaQuery( @@ -327,7 +325,7 @@ void main() { await tester.pumpAndSettle(); - signUpBloc.add( + authBloc.add( const SignUpRequest( dto: SignUpDTO( email: "email", @@ -343,18 +341,18 @@ void main() { testWidgets('SignUp BlocListener Test When Failed Occured', (WidgetTester tester) async { whenListen( - signUpBloc, + authBloc, Stream.fromIterable([ - SignUpInitial(), + AuthInitial(), SignUpLoading(), const SignUpFailed(error: "Failed occured"), ]), - initialState: SignUpInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( - BlocProvider.value( - value: signUpBloc, + BlocProvider.value( + value: authBloc, child: TestApp( routes: { '/': (_) => const MediaQuery( @@ -373,7 +371,7 @@ void main() { await tester.pumpAndSettle(); - signUpBloc.add( + authBloc.add( const SignUpRequest( dto: SignUpDTO( email: "email", diff --git a/modules/auth/test/widgets/google_sign_in_button_test.dart b/modules/auth/test/widgets/google_sign_in_button_test.dart index c6e687a..0829af5 100644 --- a/modules/auth/test/widgets/google_sign_in_button_test.dart +++ b/modules/auth/test/widgets/google_sign_in_button_test.dart @@ -1,6 +1,5 @@ import 'package:auth/auth.dart'; -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; + import 'package:bloc_test/bloc_test.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; @@ -25,31 +24,19 @@ class MockUser extends Mock implements User { MockUser({this.uid = 'id', this.displayName = 'name', this.email = 'email'}); } -class MockSignInBloc extends Mock implements SignInBloc { +class MockAuthBloc extends Mock implements AuthBloc { @override final AuthService services; @override final UserService userService; - MockSignInBloc({required this.services, required this.userService}) { - when(() => close()).thenAnswer((_) async => {}); - } -} - -class MockSignUpBloc extends Mock implements SignUpBloc { - @override - final AuthService services; - @override - final UserService userService; - - MockSignUpBloc({required this.services, required this.userService}) { + MockAuthBloc({required this.services, required this.userService}) { when(() => close()).thenAnswer((_) async => {}); } } void main() { - late SignInBloc signInBloc; - late SignUpBloc signUpBloc; + late AuthBloc authBloc; late AuthService mockAuthService; late UserService mockUserService; late GetIt getIt; @@ -73,18 +60,14 @@ void main() { mockAuthService = getIt(); mockUserService = getIt(); - signInBloc = MockSignInBloc( - services: mockAuthService, - userService: mockUserService, - ); - signUpBloc = MockSignUpBloc( + authBloc = MockAuthBloc( services: mockAuthService, userService: mockUserService, ); }); tearDown(() { - signInBloc.close(); + authBloc.close(); getIt.reset(); }); @@ -94,6 +77,7 @@ void main() { const TestApp( home: Scaffold( body: GoogleSignInButton( + type: AuthType.signIn, padding: EdgeInsets.all(8.0), margin: EdgeInsets.all(16.0), ), @@ -110,7 +94,9 @@ void main() { await tester.pumpWidget( const TestApp( home: Scaffold( - body: GoogleSignInButton(), + body: GoogleSignInButton( + type: AuthType.signIn, + ), ), ), ); @@ -126,6 +112,7 @@ void main() { const TestApp( home: Scaffold( body: GoogleSignInButton( + type: AuthType.signIn, padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 12.0), margin: EdgeInsets.all(20.0), ), @@ -141,25 +128,25 @@ void main() { }); testWidgets( - 'Widget onTap callback is call Bloc Event Correctly SignIn Bloc', + 'Widget onTap callback is call Bloc Event Correctly SignUp Bloc', (WidgetTester tester) async { whenListen( - signUpBloc, + authBloc, Stream.fromIterable([ - SignUpInitial(), + AuthInitial(), SignUpLoading(), SignUpSuccess(), ]), - initialState: SignUpInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( TestApp( - home: BlocProvider( - create: (context) => signUpBloc, + home: BlocProvider( + create: (context) => authBloc, child: const Scaffold( body: GoogleSignInButton( - bloc: SignUpBloc, + type: AuthType.signUp, ), ), ), @@ -174,32 +161,32 @@ void main() { await tester.pumpAndSettle(); verify( - () => signUpBloc.add(SignUpByGoogleRequest()), + () => authBloc.add(SignUpByGoogleRequest()), ).called(1); await tester.pumpAndSettle(); }); testWidgets( - 'Widget onTap callback is call Bloc Event Correctly SignUp Bloc', + 'Widget onTap callback is call Bloc Event Correctly Sign In Bloc', (WidgetTester tester) async { whenListen( - signInBloc, + authBloc, Stream.fromIterable([ - SignInInitial(), + AuthInitial(), SignInLoading(), SignInSuccess(), ]), - initialState: SignInInitial(), + initialState: AuthInitial(), ); await tester.pumpWidget( TestApp( - home: BlocProvider( - create: (context) => signInBloc, + home: BlocProvider( + create: (context) => authBloc, child: const Scaffold( body: GoogleSignInButton( - bloc: SignInBloc, + type: AuthType.signIn, ), ), ), @@ -214,7 +201,7 @@ void main() { await tester.pumpAndSettle(); verify( - () => signInBloc.add(SignInByGoogleRequest()), + () => authBloc.add(SignInByGoogleRequest()), ).called(1); await tester.pumpAndSettle(); diff --git a/modules/auth/test/widgets/oauth_sign_in_button_test.dart b/modules/auth/test/widgets/oauth_sign_in_button_test.dart index d13147a..c9b54b4 100644 --- a/modules/auth/test/widgets/oauth_sign_in_button_test.dart +++ b/modules/auth/test/widgets/oauth_sign_in_button_test.dart @@ -11,7 +11,14 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; await tester.pumpWidget( - const TestApp(home: Material(child: OAuthSignInButton()))); + const TestApp( + home: Material( + child: OAuthSignInButton( + type: AuthType.signIn, + ), + ), + ), + ); expect(find.byType(AppleSignInButton), findsOneWidget); expect(find.byType(GoogleSignInButton), findsNothing); @@ -23,7 +30,14 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.android; await tester.pumpWidget( - const TestApp(home: Material(child: OAuthSignInButton()))); + const TestApp( + home: Material( + child: OAuthSignInButton( + type: AuthType.signIn, + ), + ), + ), + ); expect(find.byType(GoogleSignInButton), findsOneWidget); expect(find.byType(AppleSignInButton), findsNothing); diff --git a/modules/core/assets/images/dummy_course_image.png b/modules/core/assets/images/dummy_course_image.png new file mode 100644 index 0000000..3acbd75 Binary files /dev/null and b/modules/core/assets/images/dummy_course_image.png differ diff --git a/modules/core/lib/assetsmanager/assets_manager.dart b/modules/core/lib/assetsmanager/assets_manager.dart index 75bb74e..e898f16 100644 --- a/modules/core/lib/assetsmanager/assets_manager.dart +++ b/modules/core/lib/assetsmanager/assets_manager.dart @@ -7,6 +7,7 @@ class AssetsManager { static String onboard = '$_baseImagePath/onboard.png'; static String loginGoogle = '$_baseImagePath/login_google.png'; static String loginApple = '$_baseImagePath/login_apple.png'; + static String dummyCourseImage = '$_baseImagePath/dummy_course_image.png'; // Icons Related static String emailIcon = '$_baseIconsPath/email.png'; diff --git a/modules/core/lib/constants/constants.dart b/modules/core/lib/constants/constants.dart index c321ac5..b875b9b 100644 --- a/modules/core/lib/constants/constants.dart +++ b/modules/core/lib/constants/constants.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; String getAvatarUrl(String name) { - return "https://ui-avatars.com/api/?rounded=true&name=$name"; + return "https://ui-avatars.com/api/?rounded=true&name=$name&background=EADDFF&color=21005D&size=256"; } final GlobalKey globalNavigatorKey = diff --git a/modules/core/lib/injector/bloc.dart b/modules/core/lib/injector/bloc.dart index 9c2ea3c..532b5c3 100644 --- a/modules/core/lib/injector/bloc.dart +++ b/modules/core/lib/injector/bloc.dart @@ -1,19 +1,11 @@ -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; -import 'package:auth/data/remote/auth_services.dart'; -import 'package:auth/data/remote/user_services.dart'; + +import 'package:auth/auth.dart'; import 'package:get_it/get_it.dart'; void setUpBlocDepedencies(GetIt getIt) { ///Auth Blocs - getIt.registerFactory( - () => SignInBloc( - services: getIt(), - userService: getIt(), - ), - ); - getIt.registerFactory( - () => SignUpBloc( + getIt.registerFactory( + () => AuthBloc( services: getIt(), userService: getIt(), ), diff --git a/modules/core/lib/router/app_router.dart b/modules/core/lib/router/app_router.dart index 399a9b5..aaaed0d 100644 --- a/modules/core/lib/router/app_router.dart +++ b/modules/core/lib/router/app_router.dart @@ -1,11 +1,10 @@ import 'package:app/app.dart'; -import 'package:auth/bloc/sign_in/sign_in_bloc.dart'; -import 'package:auth/bloc/sign_up/sign_up_bloc.dart'; +import 'package:auth/bloc/auth/auth_bloc.dart'; + import 'package:auth/presentation/screens/sign_in_screen.dart'; import 'package:auth/presentation/screens/sign_up_screen.dart'; import 'package:cwa_core/core.dart'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -27,13 +26,13 @@ class AppRouter { case AppRoutes.signUpScreen: return BlocProvider( - create: (context) => locator.getIt(), + create: (context) => locator.getIt(), child: const SignUpScreen(), ); case AppRoutes.signInScreen: return BlocProvider( - create: (context) => locator.getIt(), + create: (context) => locator.getIt(), child: SignInScreen(), ); diff --git a/modules/home/lib/presentation/screens/home_screen.dart b/modules/home/lib/presentation/screens/home_screen.dart index 73545d1..e6604fb 100644 --- a/modules/home/lib/presentation/screens/home_screen.dart +++ b/modules/home/lib/presentation/screens/home_screen.dart @@ -1,6 +1,8 @@ import 'package:cwa_core/core.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:percent_indicator/percent_indicator.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @@ -9,10 +11,12 @@ class HomeScreen extends StatelessWidget { Widget build(BuildContext context) { return SafeArea( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.w), + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 12.h), child: const Column( children: [ HomeUserInfo(), + HomeSearchFormField(), + HomeContinueLessonSection(), ], ), ), @@ -25,26 +29,267 @@ class HomeUserInfo extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return const Row( children: [ - Container( - width: 70.w, - height: 70.w, - decoration: const BoxDecoration( - color: bottomNavBackgroundColor, - shape: BoxShape.circle, - ), - padding: EdgeInsets.symmetric( - horizontal: 8.w, - vertical: 8.h, - ), - child: const Center( - child: CircleAvatar( - child: Text("A"), - ), + Expanded( + child: Row( + children: [ + HomeUserAvatar(), + HomeUserName(), + ], ), - ) + ), + HomeNotificationButton() ], ); } } + +class HomeUserAvatar extends StatelessWidget { + const HomeUserAvatar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: 70.w, + height: 70.w, + decoration: const BoxDecoration( + color: bottomNavBackgroundColor, + shape: BoxShape.circle, + ), + padding: EdgeInsets.symmetric( + horizontal: 8.w, + vertical: 8.h, + ), + child: Center( + child: Image.network( + "https://ui-avatars.com/api/?rounded=true&name=AlqowyShaynaXo&background=EADDFF&color=21005D&size=256", + width: 54.w, + height: 54.h, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.person, + size: 54.w, + color: const Color(0XFFEADDFF), + ); + }, + ), + ), + ); + } +} + +class HomeUserName extends StatelessWidget { + const HomeUserName({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: 100.w, + margin: EdgeInsets.only( + left: 12.w, + ), + child: Text( + "Alqowy Shayna Xo", + style: whiteTextStyle.copyWith( + fontWeight: semiBold, + fontSize: 18.sp, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } +} + +class HomeNotificationButton extends StatelessWidget { + final bool isHaveNotification; + + const HomeNotificationButton({ + super.key, + this.isHaveNotification = false, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () {}, + child: Container( + width: 55.w, + height: 55.h, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: bottomNavBackgroundColor, + ), + child: Center( + child: SizedBox( + width: 24.w, + height: 24.w, + child: Stack( + children: [ + Icon( + Icons.notifications_outlined, + color: whiteColor, + size: 24.sp, + ), + if (isHaveNotification) + Positioned( + top: 3.h, + right: 3.w, + child: Container( + width: 8.w, + height: 8.h, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.red, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class HomeSearchFormField extends StatelessWidget { + const HomeSearchFormField({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 30.h), + child: TextFormField( + cursorColor: whiteColor, + decoration: InputDecoration( + prefixIcon: Icon( + Icons.search, + color: whiteColor, + size: 24.sp, + ), + hintText: "searchBarPlaceholderText".tr(), + hintStyle: grayTextStyle.copyWith(fontSize: 16.sp), + isCollapsed: false, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(100.r), + borderSide: BorderSide.none, + ), + filled: true, + fillColor: bottomNavBackgroundColor, + contentPadding: EdgeInsets.symmetric( + vertical: 14.h, + horizontal: 20.w, + ), + ), + ), + ); + } +} + +class HomeContinueLessonSection extends StatelessWidget { + const HomeContinueLessonSection({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 30.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "homeContinueLearningText".tr(), + style: whiteTextStyle.copyWith( + fontWeight: semiBold, + fontSize: 16.sp, + ), + ), + SizedBox( + height: 6.h, + ), + Container( + width: 345.w, + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 20.h, + ), + decoration: BoxDecoration( + color: bottomNavBackgroundColor, + borderRadius: BorderRadius.circular(16.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 190.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Mastering Figma Auto Layout", + maxLines: 2, + style: whiteTextStyle.copyWith( + fontSize: 20.sp, + fontWeight: bold, + ), + ), + SizedBox( + height: 6.h, + ), + Text( + "UI/UX Design", + style: grayTextStyle, + ), + ], + ), + ), + Container( + width: 100.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + image: DecorationImage( + image: AssetImage( + AssetsManager.dummyCourseImage, + package: "cwa_core", + ), + ), + ), + ), + ], + ), + SizedBox( + height: 16.h, + ), + LinearPercentIndicator( + barRadius: Radius.circular(100.r), + width: 260.w, + padding: EdgeInsets.zero, + percent: 11 / 69, + lineHeight: 8.h, + progressColor: primaryColor, + backgroundColor: grayColor, + trailing: Container( + margin: EdgeInsets.only(left: 6.w), + child: Text( + "11/69", + style: whiteTextStyle, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/modules/home/pubspec.yaml b/modules/home/pubspec.yaml index 4e714be..1338aa3 100644 --- a/modules/home/pubspec.yaml +++ b/modules/home/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: sdk: flutter flutter_screenutil: ^5.9.0 get_it: ^7.6.6 + percent_indicator: ^4.2.3 dev_dependencies: bloc_test: ^9.1.5 diff --git a/pubspec.lock b/pubspec.lock index e96415f..0ba4830 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -162,10 +162,10 @@ packages: dependency: transitive description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.1" easy_localization: dependency: "direct main" description: @@ -657,6 +657,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + percent_indicator: + dependency: transitive + description: + name: percent_indicator + sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c + url: "https://pub.dev" + source: hosted + version: "4.2.3" platform: dependency: transitive description: