From 3faceeebb6382fe9955ff8a693a477b5a71b4751 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Mon, 6 Feb 2023 18:54:45 +0100 Subject: [PATCH] feat(authentication): add custom routine, and documentation --- .../example/lib/core/routes/router.dart | 35 ++-- .../lib/presentation/features/app/app.dart | 19 +- .../authentication/authentication_cubit.dart | 7 + .../presentation/features/sub/sub_page.dart | 16 +- .../lib/src/core/utils/custom_routine.dart | 8 +- .../cubit/authentication_cubit.dart | 170 +++++------------- .../cubit/password_reset_cubit.dart | 81 +++++---- .../sign_in/cubit/base_sign_in_cubit.dart | 2 + .../cubit/mixin/sign_in_anonymously.dart | 93 +++++----- .../mixin/sign_in_with_email_password.dart | 100 +++++------ .../cubit/mixin/sign_in_with_google.dart | 96 +++++----- .../features/sign_in/cubit/sign_in_cubit.dart | 2 + .../features/sign_in/cubit/sign_in_state.dart | 1 + .../sign_in/listener/sign_in_listener.dart | 6 +- .../sign_up/cubit/base_sign_up_cubit.dart | 2 + .../mixin/sign_up_with_email_password.dart | 107 +++++------ .../features/sign_up/cubit/sign_up_cubit.dart | 3 + .../features/sign_up/cubit/sign_up_state.dart | 1 + .../sign_up/listener/sign_up_listener.dart | 6 +- 19 files changed, 331 insertions(+), 424 deletions(-) diff --git a/packages/wyatt_authentication_bloc/example/lib/core/routes/router.dart b/packages/wyatt_authentication_bloc/example/lib/core/routes/router.dart index 51297a01..dea8429c 100644 --- a/packages/wyatt_authentication_bloc/example/lib/core/routes/router.dart +++ b/packages/wyatt_authentication_bloc/example/lib/core/routes/router.dart @@ -65,22 +65,23 @@ class AppRouter { ), ), GoRoute( - path: '/home', - name: HomePage.pageName, - pageBuilder: (context, state) => defaultTransition( - context, - state, - const HomePage(), - ), - ), - GoRoute( - path: '/home/sub', - name: SubPage.pageName, - pageBuilder: (context, state) => defaultTransition( - context, - state, - const SubPage(), - ), - ), + path: '/home', + name: HomePage.pageName, + pageBuilder: (context, state) => defaultTransition( + context, + state, + const HomePage(), + ), + routes: [ + GoRoute( + path: 'sub', + name: SubPage.pageName, + pageBuilder: (context, state) => defaultTransition( + context, + state, + const SubPage(), + ), + ), + ]), ]; } diff --git a/packages/wyatt_authentication_bloc/example/lib/presentation/features/app/app.dart b/packages/wyatt_authentication_bloc/example/lib/presentation/features/app/app.dart index 1264aa73..75388b5e 100644 --- a/packages/wyatt_authentication_bloc/example/lib/presentation/features/app/app.dart +++ b/packages/wyatt_authentication_bloc/example/lib/presentation/features/app/app.dart @@ -30,19 +30,6 @@ import 'package:go_router/go_router.dart'; import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; -// FutureOrResult onAccountChanges( -// AuthenticationRepository repo, -// AuthChangeEvent? authEvent, -// ) async { -// final id = Random().nextInt(1000); -// final token = -// await repo.getIdentityToken().fold((value) => value, (error) => 'null'); - -// debugPrint( -// 'onAccountChanges: ${authEvent?.account}, type: ${authEvent.runtimeType}, token: $token, generatedId: $id'); -// return Ok(id); -// } - class App extends StatelessWidget { final AuthenticationRepository authenticationRepository = AuthenticationRepositoryImpl( @@ -64,7 +51,9 @@ class App extends StatelessWidget { Widget build(BuildContext context) { AuthenticationState? previous; - final AuthenticationCubit authenticationCubit = ExampleAuthenticationCubit(authenticationRepository: authenticationRepository); + final AuthenticationCubit authenticationCubit = + ExampleAuthenticationCubit( + authenticationRepository: authenticationRepository); final GoRouter router = GoRouter( initialLocation: '/', @@ -91,7 +80,7 @@ class App extends StatelessWidget { return '/home'; } } - return null; + return state.name; }, ); diff --git a/packages/wyatt_authentication_bloc/example/lib/presentation/features/authentication/authentication_cubit.dart b/packages/wyatt_authentication_bloc/example/lib/presentation/features/authentication/authentication_cubit.dart index 869be269..5f03c2f7 100644 --- a/packages/wyatt_authentication_bloc/example/lib/presentation/features/authentication/authentication_cubit.dart +++ b/packages/wyatt_authentication_bloc/example/lib/presentation/features/authentication/authentication_cubit.dart @@ -49,4 +49,11 @@ class ExampleAuthenticationCubit extends AuthenticationCubit { return const Ok(null); } + + @override + FutureOrResult onDelete() { + print('onDelete'); + + return const Ok(null); + } } diff --git a/packages/wyatt_authentication_bloc/example/lib/presentation/features/sub/sub_page.dart b/packages/wyatt_authentication_bloc/example/lib/presentation/features/sub/sub_page.dart index 9cdfad53..9c0f4ece 100644 --- a/packages/wyatt_authentication_bloc/example/lib/presentation/features/sub/sub_page.dart +++ b/packages/wyatt_authentication_bloc/example/lib/presentation/features/sub/sub_page.dart @@ -35,14 +35,20 @@ class SubPage extends StatelessWidget { icon: const Icon(Icons.logout_rounded)), IconButton( onPressed: () => - context.read>().refresh(), + context.read>().refresh(), icon: const Icon(Icons.refresh)) ], ), - body: const Padding( - padding: EdgeInsets.all(8), - child: SingleChildScrollView( - child: Text('Another page'), + body: Padding( + padding: const EdgeInsets.all(8), + child: ListView( + children: [ + const Text('Another page'), + ElevatedButton( + onPressed: () => context.read>().delete(), + child: const Text('Delete account'), + ), + ], ), ), ); diff --git a/packages/wyatt_authentication_bloc/lib/src/core/utils/custom_routine.dart b/packages/wyatt_authentication_bloc/lib/src/core/utils/custom_routine.dart index db8b4265..bdd1eb7a 100644 --- a/packages/wyatt_authentication_bloc/lib/src/core/utils/custom_routine.dart +++ b/packages/wyatt_authentication_bloc/lib/src/core/utils/custom_routine.dart @@ -35,7 +35,7 @@ class CustomRoutine { final FutureOr> Function( Result routineResult, ) attachedLogic; - final void Function(AppException? exception) onError; + final void Function(AppException exception) onError; final void Function(R result, Data? data) onSuccess; FutureOr call() async { @@ -46,17 +46,17 @@ class CustomRoutine { // Check for errors if (result.isErr) { - onError.call(result.err); + onError.call(result.err!); return; } if (customRoutineResult.isErr) { - onError.call(customRoutineResult.err); + onError.call(customRoutineResult.err!); return; } // If no error - return onSuccess.call(result.ok as Input, customRoutineResult.ok); + return onSuccess.call(result.ok as R, customRoutineResult.ok); } } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/authentication/cubit/authentication_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/authentication/cubit/authentication_cubit.dart index 8a7dc406..c90ab44f 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/authentication/cubit/authentication_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/authentication/cubit/authentication_cubit.dart @@ -42,7 +42,7 @@ abstract class AuthenticationCubit } final AuthenticationRepository authenticationRepository; - SessionWrapper? latestSession; + SessionWrapper? _latestSession; void _listenForAuthenticationChanges() { authenticationRepository.sessionStream().asyncMap((wrapper) async { @@ -67,7 +67,7 @@ abstract class AuthenticationCubit } return wrapper; }).listen((wrapper) async { - latestSession = wrapper; + _latestSession = wrapper; final session = wrapper.session; if (session != null) { emit(AuthenticationState.authenticated(wrapper)); @@ -79,143 +79,59 @@ abstract class AuthenticationCubit } /// {@macro refresh} - FutureOr refresh() async { - final refreshedAccount = await authenticationRepository.refresh(); - final customRoutineResult = await onRefresh(refreshedAccount); - - if (refreshedAccount.isOk && customRoutineResult.isOk) { - final account = refreshedAccount.ok!; - final sessionData = customRoutineResult.ok; - - final refreshedSession = SessionWrapper( - event: RefreshedEvent(account: account), - session: Session( - account: account, - data: sessionData, + FutureOr refresh() async => CustomRoutine( + routine: authenticationRepository.refresh, + attachedLogic: onRefresh, + onError: addError, + onSuccess: (result, data) => authenticationRepository.addSession( + SessionWrapper( + event: RefreshedEvent(account: result), + session: Session( + account: result, + data: data, + ), ), - ); - - authenticationRepository.addSession(refreshedSession); - - return; - } - - if (refreshedAccount.isErr) { - addError(refreshedAccount.err!); - - return; - } - - if (customRoutineResult.isErr) { - addError(customRoutineResult.err!); - - return; - } - } + ), + ).call(); /// {@macro reauthenticate} - FutureOr reauthenticate() async { - final reauthenticatedAccount = - await authenticationRepository.reauthenticate(); - final customRoutineResult = await onReauthenticate(reauthenticatedAccount); - - if (reauthenticatedAccount.isOk && customRoutineResult.isOk) { - final account = reauthenticatedAccount.ok!; - final sessionData = customRoutineResult.ok; - - final reauthenticatedSession = SessionWrapper( - event: ReauthenticatedEvent(account: account), - session: Session( - account: account, - data: sessionData, + FutureOr reauthenticate() async => CustomRoutine( + routine: authenticationRepository.reauthenticate, + attachedLogic: onReauthenticate, + onError: addError, + onSuccess: (result, data) => authenticationRepository.addSession( + SessionWrapper( + event: ReauthenticatedEvent(account: result), + session: Session( + account: result, + data: data, + ), + ), ), - ); - - authenticationRepository.addSession(reauthenticatedSession); - - return; - } - - if (reauthenticatedAccount.isErr) { - addError(reauthenticatedAccount.err!); - - return; - } - - if (customRoutineResult.isErr) { - addError(customRoutineResult.err!); - - return; - } - } + ).call(); /// {@macro signout} - FutureOr signOut() async { - final customRoutine = CustomRoutine( - // ignore: unnecessary_lambdas - routine: () => authenticationRepository.signOut(), - attachedLogic: (routineResult) => onSignOut(), - onError: (error) => addError(error!), - onSuccess: (result, data) => authenticationRepository - .addSession(const SessionWrapper(event: SignedOutEvent())), - ); - - customRoutine.call(); - - final signOut = await authenticationRepository.signOut(); - - final customRoutineResult = await onSignOut(); - - if (signOut.isOk && customRoutineResult.isOk) { - authenticationRepository - .addSession(const SessionWrapper(event: SignedOutEvent())); - - return; - } - - if (signOut.isErr) { - addError(signOut.err!); - - return; - } - - if (customRoutineResult.isErr) { - addError(customRoutineResult.err!); - - return; - } - } + FutureOr signOut() async => CustomRoutine( + routine: authenticationRepository.signOut, + attachedLogic: (routineResult) => onSignOut(), + onError: addError, + onSuccess: (result, data) => authenticationRepository + .addSession(SessionWrapper(event: const SignedOutEvent())), + ).call(); /// {@macro delete} - FutureOr delete() async { - final signOut = await authenticationRepository.delete(); - - final customRoutineResult = await onDelete(); - - if (signOut.isOk && customRoutineResult.isOk) { - authenticationRepository - .addSession(const SessionWrapper(event: DeletedEvent())); - - return; - } - - if (signOut.isErr) { - addError(signOut.err!); - - return; - } - - if (customRoutineResult.isErr) { - addError(customRoutineResult.err!); - - return; - } - } + FutureOr delete() async => CustomRoutine( + routine: authenticationRepository.delete, + attachedLogic: (routineResult) => onDelete(), + onError: addError, + onSuccess: (result, data) => authenticationRepository + .addSession(SessionWrapper(event: const DeletedEvent())), + ).call(); /// Returns latest session wrapper. /// /// Contains latest event and latest session data (account + extra data) - SessionWrapper? currentSession() => latestSession; + SessionWrapper? currentSession() => _latestSession; /// This callback is triggered when the user is automaticcaly logged in from /// the cache. diff --git a/packages/wyatt_authentication_bloc/lib/src/features/password_reset/cubit/password_reset_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/password_reset/cubit/password_reset_cubit.dart index 160f10db..c8624fc6 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/password_reset/cubit/password_reset_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/password_reset/cubit/password_reset_cubit.dart @@ -24,28 +24,22 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; part 'password_reset_state.dart'; +/// Cubit that allows user to reset his password class PasswordResetCubit extends FormDataCubit { PasswordResetCubit({ required this.authenticationRepository, - }) : - super( + }) : super( PasswordResetState( form: authenticationRepository.formRepository .accessForm(AuthFormName.passwordResetForm), ), ); final AuthenticationRepository authenticationRepository; - FormRepository get formRepository => - authenticationRepository.formRepository; + FormRepository get formRepository => authenticationRepository.formRepository; @override String get formName => AuthFormName.passwordResetForm; - void emailChanged(String value) { - final Email email = Email.dirty(value); - dataChanged(AuthFormField.email, email); - } - @override FutureOr dataChanged( String key, @@ -74,6 +68,33 @@ class PasswordResetCubit extends FormDataCubit { ); } + @override + FutureOr update( + WyattForm form, { + SetOperation operation = SetOperation.replace, + }) { + final WyattForm current = formRepository.accessForm(formName).clone(); + final WyattForm newForm = operation.operation.call(current, form); + formRepository.updateForm(newForm); + + emit( + state.copyWith( + form: newForm, + status: newForm.validate(), + ), + ); + } + + @override + FutureOr validate() { + emit( + state.copyWith( + status: formRepository.accessForm(formName).validate(), + ), + ); + } + + /// Sends a password reset email to the user @override FutureOr submit() async { if (!state.status.isValidated) { @@ -109,29 +130,29 @@ class PasswordResetCubit extends FormDataCubit { ); } - @override - FutureOr update( - WyattForm form, { - SetOperation operation = SetOperation.replace, - }) { - final WyattForm current = formRepository.accessForm(formName).clone(); - final WyattForm newForm = operation.operation.call(current, form); - formRepository.updateForm(newForm); - - emit( - state.copyWith( - form: newForm, - status: newForm.validate(), - ), + void emailChanged(String value) { + final emailValidatorType = formRepository + .accessForm(formName) + .validatorOf(AuthFormField.email) + .runtimeType; + assert( + emailValidatorType == Email, + 'Use emailCustomChanged(...) with validator $emailValidatorType', ); + + final Email email = Email.dirty(value); + dataChanged(AuthFormField.email, email); } - @override - FutureOr validate() { - emit( - state.copyWith( - status: formRepository.accessForm(formName).validate(), - ), - ); + /// Same as [emailChanged] but with a custom [Validator]. + /// + /// Sort of short hand for [dataChanged]. + void emailCustomChanged< + Validator extends FormInputValidator>( + Validator validator, + ) { + dataChanged(AuthFormField.email, validator); } + + // TODO(wyatt): create base_password_reset_cubit and create mixins } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/base_sign_in_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/base_sign_in_cubit.dart index 03392126..636a2786 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/base_sign_in_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/base_sign_in_cubit.dart @@ -16,6 +16,8 @@ part of 'sign_in_cubit.dart'; +/// Abstract sign in cubit useful for implementing a cubit with fine +/// granularity by adding only the required mixins. abstract class BaseSignInCubit extends FormDataCubit { BaseSignInCubit({ required this.authenticationRepository, diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_anonymously.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_anonymously.dart index 97df97e0..2adf2f2f 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_anonymously.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_anonymously.dart @@ -17,17 +17,26 @@ import 'dart:async'; import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart'; import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; +/// Sign in mixin. +/// +/// Allows the user to sign in anonymously +/// +/// Gives access to the `signInAnonymously` method and +/// `onSignInAnonymously` callback. mixin SignInAnonymously on BaseSignInCubit { + /// This callback is triggered when a user signs in anonymously. FutureOrResult onSignInAnonymously( Result result, WyattForm form, ); + /// {@macro signin_anom} FutureOr signInAnonymously() async { if (state.status.isSubmissionInProgress) { return; @@ -36,57 +45,39 @@ mixin SignInAnonymously on BaseSignInCubit { final form = formRepository.accessForm(formName); emit(SignInState(form: form, status: FormStatus.submissionInProgress)); - final result = await authenticationRepository.signInAnonymously(); - - // Custom routine - final customRoutineResult = await onSignInAnonymously(result, form); - if (customRoutineResult.isErr) { - final error = customRoutineResult.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - // Check result - if (result.isErr) { - final error = result.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - final account = result.ok!; - - final signedInSession = SessionWrapper( - event: SignedInEvent(account: account), - session: Session( - account: account, - data: customRoutineResult.ok, + return CustomRoutine( + routine: authenticationRepository.signInAnonymously, + attachedLogic: (routineResult) => onSignInAnonymously( + routineResult, + form, ), - ); - - authenticationRepository.addSession(signedInSession); - - emit( - result.fold( - (value) => - SignInState(form: form, status: FormStatus.submissionSuccess), - (error) => SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ), - ); + onError: (error) { + emit( + SignInState( + form: form, + errorMessage: error.message, + status: FormStatus.submissionFailure, + ), + ); + addError(error); + }, + onSuccess: (account, data) { + authenticationRepository.addSession( + SessionWrapper( + event: SignedInEvent(account: account), + session: Session( + account: account, + data: data, + ), + ), + ); + emit( + SignInState( + form: form, + status: FormStatus.submissionSuccess, + ), + ); + }, + ).call(); } } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_email_password.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_email_password.dart index 4c868911..36e7372b 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_email_password.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_email_password.dart @@ -18,12 +18,20 @@ import 'dart:async'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; +import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart'; import 'package:wyatt_authentication_bloc/src/domain/domain.dart'; import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; +/// Sign in mixin. +/// +/// Allows the user to sign in with email and password +/// +/// Gives access to the `signInWithEmailAndPassword` method and +/// `onSignInWithEmailAndPassword` callback. mixin SignInWithEmailPassword on BaseSignInCubit { + /// This callback is triggered when a user signs in with email and password. FutureOrResult onSignInWithEmailAndPassword( Result result, WyattForm form, @@ -76,6 +84,7 @@ mixin SignInWithEmailPassword on BaseSignInCubit { dataChanged(AuthFormField.password, validator); } + /// {@macro signin_pwd} FutureOr signInWithEmailAndPassword() async { if (state.status.isSubmissionInProgress) { return; @@ -106,63 +115,42 @@ mixin SignInWithEmailPassword on BaseSignInCubit { ); } - final result = await authenticationRepository.signInWithEmailAndPassword( - email: email!, - password: password!, - ); - - // Custom routine - final customRoutineResult = await onSignInWithEmailAndPassword( - result, - form, - ); - if (customRoutineResult.isErr) { - final error = customRoutineResult.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - // Check result - if (result.isErr) { - final error = result.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - final account = result.ok!; - - final signedInSession = SessionWrapper( - event: SignedInEvent(account: account), - session: Session( - account: account, - data: customRoutineResult.ok, + return CustomRoutine( + routine: () => authenticationRepository.signInWithEmailAndPassword( + email: email!, + password: password!, ), - ); - - authenticationRepository.addSession(signedInSession); - - emit( - result.fold( - (value) => - SignInState(form: form, status: FormStatus.submissionSuccess), - (error) => SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), + attachedLogic: (routineResult) => onSignInWithEmailAndPassword( + routineResult, + form, ), - ); + onError: (error) { + emit( + SignInState( + form: form, + errorMessage: error.message, + status: FormStatus.submissionFailure, + ), + ); + addError(error); + }, + onSuccess: (account, data) { + authenticationRepository.addSession( + SessionWrapper( + event: SignedInEvent(account: account), + session: Session( + account: account, + data: data, + ), + ), + ); + emit( + SignInState( + form: form, + status: FormStatus.submissionSuccess, + ), + ); + }, + ).call(); } } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_google.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_google.dart index 444e35c3..458844fe 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_google.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/mixin/sign_in_with_google.dart @@ -17,17 +17,26 @@ import 'dart:async'; import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart'; import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; +/// Sign in mixin. +/// +/// Allows the user to sign in with google +/// +/// Gives access to the `signInWithGoogle` method and +/// `onSignInWithGoogle` callback. mixin SignInWithGoogle on BaseSignInCubit { + /// This callback is triggered when a user signs in with google. FutureOrResult onSignInWithGoogle( Result result, WyattForm form, ); + /// {@macro signin_google} FutureOr signInWithGoogle() async { if (state.status.isSubmissionInProgress) { return; @@ -35,60 +44,39 @@ mixin SignInWithGoogle on BaseSignInCubit { final form = formRepository.accessForm(formName); emit(SignInState(form: form, status: FormStatus.submissionInProgress)); - final result = await authenticationRepository.signInWithGoogle(); - - // Custom routine - final customRoutineResult = await onSignInWithGoogle( - result, - form, - ); - if (customRoutineResult.isErr) { - final error = customRoutineResult.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - // Check result - if (result.isErr) { - final error = result.err!; - emit( - SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - final account = result.ok!; - - final signedInSession = SessionWrapper( - event: SignedInEvent(account: account), - session: Session( - account: account, - data: customRoutineResult.ok, + return CustomRoutine( + routine: authenticationRepository.signInWithGoogle, + attachedLogic: (routineResult) => onSignInWithGoogle( + routineResult, + form, ), - ); - - authenticationRepository.addSession(signedInSession); - - emit( - result.fold( - (value) => - SignInState(form: form, status: FormStatus.submissionSuccess), - (error) => SignInState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ), - ); + onError: (error) { + emit( + SignInState( + form: form, + errorMessage: error.message, + status: FormStatus.submissionFailure, + ), + ); + addError(error); + }, + onSuccess: (account, data) { + authenticationRepository.addSession( + SessionWrapper( + event: SignedInEvent(account: account), + session: Session( + account: account, + data: data, + ), + ), + ); + emit( + SignInState( + form: form, + status: FormStatus.submissionSuccess, + ), + ); + }, + ).call(); } } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_cubit.dart index b67f4db6..f1f3ccd5 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_cubit.dart @@ -32,6 +32,8 @@ part 'base_sign_in_cubit.dart'; part 'sign_in_state.dart'; /// Fully featured sign in cubit. +/// +/// Sufficient in most cases. (Where fine granularity is not required.) class SignInCubit extends BaseSignInCubit with SignInAnonymously, diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_state.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_state.dart index 18f5b6da..acb975b5 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_state.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/cubit/sign_in_state.dart @@ -16,6 +16,7 @@ part of 'sign_in_cubit.dart'; +/// Sign in cubit state to manage the form. class SignInState extends FormDataState { const SignInState({ required super.form, diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/listener/sign_in_listener.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/listener/sign_in_listener.dart index d399142e..cc66ac36 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_in/listener/sign_in_listener.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_in/listener/sign_in_listener.dart @@ -19,7 +19,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:wyatt_authentication_bloc/src/features/sign_in/sign_in.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; -class SignInListener extends StatelessWidget { +/// Widget that listens and builds a child based on the state of +/// the sign in cubit +class SignInListener extends StatelessWidget { const SignInListener({ required this.child, this.onProgress, @@ -41,7 +43,7 @@ class SignInListener extends StatelessWidget { @override Widget build(BuildContext context) => - BlocListener, SignInState>( + BlocListener, SignInState>( listener: (context, state) { if (customBuilder != null) { return customBuilder!(context, state); diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/base_sign_up_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/base_sign_up_cubit.dart index 3040d1ab..42747ce3 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/base_sign_up_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/base_sign_up_cubit.dart @@ -16,6 +16,8 @@ part of 'sign_up_cubit.dart'; +/// Abstract sign up cubit useful for implementing a cubit with fine +/// granularity by adding only the required mixins. abstract class BaseSignUpCubit extends FormDataCubit { BaseSignUpCubit({ required this.authenticationRepository, diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/mixin/sign_up_with_email_password.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/mixin/sign_up_with_email_password.dart index 9c17deac..7c2f39f0 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/mixin/sign_up_with_email_password.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/mixin/sign_up_with_email_password.dart @@ -17,13 +17,20 @@ import 'dart:async'; import 'package:wyatt_architecture/wyatt_architecture.dart'; -import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; -import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart'; -import 'package:wyatt_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; +/// Sign up mixin. +/// +/// Allows the user to register with an email and a password. +/// +/// Gives access to the `signUpWithEmailPassword` method and +/// `onSignUpWithEmailAndPassword` callback. mixin SignUpWithEmailPassword on BaseSignUpCubit { + /// This callback is triggered when a user creates an account. + /// + /// For example: when a user sign up in firebase. FutureOrResult onSignUpWithEmailAndPassword( Result result, WyattForm form, @@ -76,6 +83,7 @@ mixin SignUpWithEmailPassword on BaseSignUpCubit { dataChanged(AuthFormField.password, validator); } + /// {@macro signup_pwd} FutureOr signUpWithEmailPassword() async { if (!state.status.isValidated) { return; @@ -97,65 +105,42 @@ mixin SignUpWithEmailPassword on BaseSignUpCubit { ); } - final result = await authenticationRepository.signUpWithEmailAndPassword( - email: email!, - password: password!, - ); - - // Custom routine - final customRoutineResult = await onSignUpWithEmailAndPassword( - result, - form, - ); - if (customRoutineResult.isErr) { - final error = customRoutineResult.err!; - emit( - SignUpState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - // Check result - if (result.isErr) { - final error = result.err!; - emit( - SignUpState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), - ); - addError(error); - } - - final account = result.ok!; - - final signedUpSession = SessionWrapper( - event: SignedUpEvent(account: account), - session: Session( - account: account, - data: customRoutineResult.ok, + return CustomRoutine( + routine: () => authenticationRepository.signUpWithEmailAndPassword( + email: email!, + password: password!, ), - ); - - authenticationRepository.addSession(signedUpSession); - - emit( - result.fold( - (value) => SignUpState( - form: form, - status: FormStatus.submissionSuccess, - ), - (error) => SignUpState( - form: form, - errorMessage: error.message, - status: FormStatus.submissionFailure, - ), + attachedLogic: (routineResult) => onSignUpWithEmailAndPassword( + routineResult, + form, ), - ); + onError: (error) { + emit( + SignUpState( + form: form, + errorMessage: error.message, + status: FormStatus.submissionFailure, + ), + ); + addError(error); + }, + onSuccess: (account, data) { + authenticationRepository.addSession( + SessionWrapper( + event: SignedUpEvent(account: account), + session: Session( + account: account, + data: data, + ), + ), + ); + emit( + SignUpState( + form: form, + status: FormStatus.submissionSuccess, + ), + ); + }, + ).call(); } } diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_cubit.dart index 49052a16..75c285cf 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_cubit.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_cubit.dart @@ -28,6 +28,9 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; part 'base_sign_up_cubit.dart'; part 'sign_up_state.dart'; +/// Fully featured sign up cubit. +/// +/// Sufficient in most cases. (Where fine granularity is not required.) class SignUpCubit extends BaseSignUpCubit with SignUpWithEmailPassword { SignUpCubit({required super.authenticationRepository}); diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_state.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_state.dart index 3edd1fe5..a66a3693 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_state.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/cubit/sign_up_state.dart @@ -16,6 +16,7 @@ part of 'sign_up_cubit.dart'; +/// Sign up cubit state to manage the form. class SignUpState extends FormDataState { const SignUpState({ required super.form, diff --git a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/listener/sign_up_listener.dart b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/listener/sign_up_listener.dart index 1e4cc616..1493df64 100644 --- a/packages/wyatt_authentication_bloc/lib/src/features/sign_up/listener/sign_up_listener.dart +++ b/packages/wyatt_authentication_bloc/lib/src/features/sign_up/listener/sign_up_listener.dart @@ -19,7 +19,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:wyatt_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; -class SignUpListener extends StatelessWidget { +/// Widget that listens and builds a child based on the state of +/// the sign up cubit +class SignUpListener extends StatelessWidget { const SignUpListener({ required this.child, this.onProgress, @@ -41,7 +43,7 @@ class SignUpListener extends StatelessWidget { @override Widget build(BuildContext context) => - BlocListener, SignUpState>( + BlocListener, SignUpState>( listener: (context, state) { if (customBuilder != null) { return customBuilder!(context, state);