master #81

Closed
malo wants to merge 322 commits from master into feat/bloc_layout/new-package
19 changed files with 331 additions and 424 deletions
Showing only changes of commit 3faceeebb6 - Show all commits

View File

@ -65,22 +65,23 @@ class AppRouter {
), ),
), ),
GoRoute( GoRoute(
path: '/home', path: '/home',
name: HomePage.pageName, name: HomePage.pageName,
pageBuilder: (context, state) => defaultTransition( pageBuilder: (context, state) => defaultTransition(
context, context,
state, state,
const HomePage(), const HomePage(),
), ),
), routes: [
GoRoute( GoRoute(
path: '/home/sub', path: 'sub',
name: SubPage.pageName, name: SubPage.pageName,
pageBuilder: (context, state) => defaultTransition( pageBuilder: (context, state) => defaultTransition(
context, context,
state, state,
const SubPage(), const SubPage(),
), ),
), ),
]),
]; ];
} }

View File

@ -30,19 +30,6 @@ import 'package:go_router/go_router.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
// FutureOrResult<int?> onAccountChanges(
// AuthenticationRepository<int> 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<int, AppException>(id);
// }
class App extends StatelessWidget { class App extends StatelessWidget {
final AuthenticationRepository<int> authenticationRepository = final AuthenticationRepository<int> authenticationRepository =
AuthenticationRepositoryImpl( AuthenticationRepositoryImpl(
@ -64,7 +51,9 @@ class App extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
AuthenticationState<int>? previous; AuthenticationState<int>? previous;
final AuthenticationCubit<int> authenticationCubit = ExampleAuthenticationCubit(authenticationRepository: authenticationRepository); final AuthenticationCubit<int> authenticationCubit =
ExampleAuthenticationCubit(
authenticationRepository: authenticationRepository);
final GoRouter router = GoRouter( final GoRouter router = GoRouter(
initialLocation: '/', initialLocation: '/',
@ -91,7 +80,7 @@ class App extends StatelessWidget {
return '/home'; return '/home';
} }
} }
return null; return state.name;
}, },
); );

View File

@ -49,4 +49,11 @@ class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
return const Ok(null); return const Ok(null);
} }
@override
FutureOrResult<void> onDelete() {
print('onDelete');
return const Ok(null);
}
} }

View File

@ -35,14 +35,20 @@ class SubPage extends StatelessWidget {
icon: const Icon(Icons.logout_rounded)), icon: const Icon(Icons.logout_rounded)),
IconButton( IconButton(
onPressed: () => onPressed: () =>
context.read<AuthenticationRepository<int>>().refresh(), context.read<AuthenticationCubit<int>>().refresh(),
icon: const Icon(Icons.refresh)) icon: const Icon(Icons.refresh))
], ],
), ),
body: const Padding( body: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: SingleChildScrollView( child: ListView(
child: Text('Another page'), children: [
const Text('Another page'),
ElevatedButton(
onPressed: () => context.read<AuthenticationCubit<int>>().delete(),
child: const Text('Delete account'),
),
],
), ),
), ),
); );

View File

@ -35,7 +35,7 @@ class CustomRoutine<R, Data> {
final FutureOr<Result<Data?, AppException>> Function( final FutureOr<Result<Data?, AppException>> Function(
Result<R, AppException> routineResult, Result<R, AppException> routineResult,
) attachedLogic; ) attachedLogic;
final void Function(AppException? exception) onError; final void Function(AppException exception) onError;
final void Function(R result, Data? data) onSuccess; final void Function(R result, Data? data) onSuccess;
FutureOr<void> call() async { FutureOr<void> call() async {
@ -46,17 +46,17 @@ class CustomRoutine<R, Data> {
// Check for errors // Check for errors
if (result.isErr) { if (result.isErr) {
onError.call(result.err); onError.call(result.err!);
return; return;
} }
if (customRoutineResult.isErr) { if (customRoutineResult.isErr) {
onError.call(customRoutineResult.err); onError.call(customRoutineResult.err!);
return; return;
} }
// If no error // If no error
return onSuccess.call(result.ok as Input, customRoutineResult.ok); return onSuccess.call(result.ok as R, customRoutineResult.ok);
} }
} }

View File

@ -42,7 +42,7 @@ abstract class AuthenticationCubit<Data>
} }
final AuthenticationRepository<Data> authenticationRepository; final AuthenticationRepository<Data> authenticationRepository;
SessionWrapper<Data>? latestSession; SessionWrapper<Data>? _latestSession;
void _listenForAuthenticationChanges() { void _listenForAuthenticationChanges() {
authenticationRepository.sessionStream().asyncMap((wrapper) async { authenticationRepository.sessionStream().asyncMap((wrapper) async {
@ -67,7 +67,7 @@ abstract class AuthenticationCubit<Data>
} }
return wrapper; return wrapper;
}).listen((wrapper) async { }).listen((wrapper) async {
latestSession = wrapper; _latestSession = wrapper;
final session = wrapper.session; final session = wrapper.session;
if (session != null) { if (session != null) {
emit(AuthenticationState<Data>.authenticated(wrapper)); emit(AuthenticationState<Data>.authenticated(wrapper));
@ -79,143 +79,59 @@ abstract class AuthenticationCubit<Data>
} }
/// {@macro refresh} /// {@macro refresh}
FutureOr<void> refresh() async { FutureOr<void> refresh() async => CustomRoutine<Account, Data?>(
final refreshedAccount = await authenticationRepository.refresh(); routine: authenticationRepository.refresh,
final customRoutineResult = await onRefresh(refreshedAccount); attachedLogic: onRefresh,
onError: addError,
if (refreshedAccount.isOk && customRoutineResult.isOk) { onSuccess: (result, data) => authenticationRepository.addSession(
final account = refreshedAccount.ok!; SessionWrapper(
final sessionData = customRoutineResult.ok; event: RefreshedEvent(account: result),
session: Session<Data>(
final refreshedSession = SessionWrapper( account: result,
event: RefreshedEvent(account: account), data: data,
session: Session<Data>( ),
account: account,
data: sessionData,
), ),
); ),
).call();
authenticationRepository.addSession(refreshedSession);
return;
}
if (refreshedAccount.isErr) {
addError(refreshedAccount.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
/// {@macro reauthenticate} /// {@macro reauthenticate}
FutureOr<void> reauthenticate() async { FutureOr<void> reauthenticate() async => CustomRoutine<Account, Data?>(
final reauthenticatedAccount = routine: authenticationRepository.reauthenticate,
await authenticationRepository.reauthenticate(); attachedLogic: onReauthenticate,
final customRoutineResult = await onReauthenticate(reauthenticatedAccount); onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession(
if (reauthenticatedAccount.isOk && customRoutineResult.isOk) { SessionWrapper(
final account = reauthenticatedAccount.ok!; event: ReauthenticatedEvent(account: result),
final sessionData = customRoutineResult.ok; session: Session<Data>(
account: result,
final reauthenticatedSession = SessionWrapper( data: data,
event: ReauthenticatedEvent(account: account), ),
session: Session<Data>( ),
account: account,
data: sessionData,
), ),
); ).call();
authenticationRepository.addSession(reauthenticatedSession);
return;
}
if (reauthenticatedAccount.isErr) {
addError(reauthenticatedAccount.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
/// {@macro signout} /// {@macro signout}
FutureOr<void> signOut() async { FutureOr<void> signOut() async => CustomRoutine<void, void>(
final customRoutine = CustomRoutine<void, void>( routine: authenticationRepository.signOut,
// ignore: unnecessary_lambdas attachedLogic: (routineResult) => onSignOut(),
routine: () => authenticationRepository.signOut(), onError: addError,
attachedLogic: (routineResult) => onSignOut(), onSuccess: (result, data) => authenticationRepository
onError: (error) => addError(error!), .addSession(SessionWrapper<Data>(event: const SignedOutEvent())),
onSuccess: (result, data) => authenticationRepository ).call();
.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;
}
}
/// {@macro delete} /// {@macro delete}
FutureOr<void> delete() async { FutureOr<void> delete() async => CustomRoutine<void, void>(
final signOut = await authenticationRepository.delete(); routine: authenticationRepository.delete,
attachedLogic: (routineResult) => onDelete(),
final customRoutineResult = await onDelete(); onError: addError,
onSuccess: (result, data) => authenticationRepository
if (signOut.isOk && customRoutineResult.isOk) { .addSession(SessionWrapper<Data>(event: const DeletedEvent())),
authenticationRepository ).call();
.addSession(const SessionWrapper(event: DeletedEvent()));
return;
}
if (signOut.isErr) {
addError(signOut.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
/// Returns latest session wrapper. /// Returns latest session wrapper.
/// ///
/// Contains latest event and latest session data (account + extra data) /// Contains latest event and latest session data (account + extra data)
SessionWrapper<Data>? currentSession() => latestSession; SessionWrapper<Data>? currentSession() => _latestSession;
/// This callback is triggered when the user is automaticcaly logged in from /// This callback is triggered when the user is automaticcaly logged in from
/// the cache. /// the cache.

View File

@ -24,28 +24,22 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'password_reset_state.dart'; part 'password_reset_state.dart';
/// Cubit that allows user to reset his password
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> { class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
PasswordResetCubit({ PasswordResetCubit({
required this.authenticationRepository, required this.authenticationRepository,
}) : }) : super(
super(
PasswordResetState( PasswordResetState(
form: authenticationRepository.formRepository form: authenticationRepository.formRepository
.accessForm(AuthFormName.passwordResetForm), .accessForm(AuthFormName.passwordResetForm),
), ),
); );
final AuthenticationRepository<Extra> authenticationRepository; final AuthenticationRepository<Extra> authenticationRepository;
FormRepository get formRepository => FormRepository get formRepository => authenticationRepository.formRepository;
authenticationRepository.formRepository;
@override @override
String get formName => AuthFormName.passwordResetForm; String get formName => AuthFormName.passwordResetForm;
void emailChanged(String value) {
final Email email = Email.dirty(value);
dataChanged(AuthFormField.email, email);
}
@override @override
FutureOr<void> dataChanged<Value>( FutureOr<void> dataChanged<Value>(
String key, String key,
@ -74,6 +68,33 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
); );
} }
@override
FutureOr<void> 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<void> validate() {
emit(
state.copyWith(
status: formRepository.accessForm(formName).validate(),
),
);
}
/// Sends a password reset email to the user
@override @override
FutureOr<void> submit() async { FutureOr<void> submit() async {
if (!state.status.isValidated) { if (!state.status.isValidated) {
@ -109,29 +130,29 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
); );
} }
@override void emailChanged(String value) {
FutureOr<void> update( final emailValidatorType = formRepository
WyattForm form, { .accessForm(formName)
SetOperation operation = SetOperation.replace, .validatorOf(AuthFormField.email)
}) { .runtimeType;
final WyattForm current = formRepository.accessForm(formName).clone(); assert(
final WyattForm newForm = operation.operation.call(current, form); emailValidatorType == Email,
formRepository.updateForm(newForm); 'Use emailCustomChanged(...) with validator $emailValidatorType',
emit(
state.copyWith(
form: newForm,
status: newForm.validate(),
),
); );
final Email email = Email.dirty(value);
dataChanged(AuthFormField.email, email);
} }
@override /// Same as [emailChanged] but with a custom [Validator].
FutureOr<void> validate() { ///
emit( /// Sort of short hand for [dataChanged].
state.copyWith( void emailCustomChanged<
status: formRepository.accessForm(formName).validate(), Validator extends FormInputValidator<String?, ValidationError>>(
), Validator validator,
); ) {
dataChanged(AuthFormField.email, validator);
} }
// TODO(wyatt): create base_password_reset_cubit and create mixins
} }

View File

@ -16,6 +16,8 @@
part of 'sign_in_cubit.dart'; 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<Data> extends FormDataCubit<SignInState> { abstract class BaseSignInCubit<Data> extends FormDataCubit<SignInState> {
BaseSignInCubit({ BaseSignInCubit({
required this.authenticationRepository, required this.authenticationRepository,

View File

@ -17,17 +17,26 @@
import 'dart:async'; import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart'; 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/domain/entities/entities.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.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_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.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<Data> on BaseSignInCubit<Data> { mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in anonymously.
FutureOrResult<Data?> onSignInAnonymously( FutureOrResult<Data?> onSignInAnonymously(
Result<Account, AppException> result, Result<Account, AppException> result,
WyattForm form, WyattForm form,
); );
/// {@macro signin_anom}
FutureOr<void> signInAnonymously() async { FutureOr<void> signInAnonymously() async {
if (state.status.isSubmissionInProgress) { if (state.status.isSubmissionInProgress) {
return; return;
@ -36,57 +45,39 @@ mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
final form = formRepository.accessForm(formName); final form = formRepository.accessForm(formName);
emit(SignInState(form: form, status: FormStatus.submissionInProgress)); emit(SignInState(form: form, status: FormStatus.submissionInProgress));
final result = await authenticationRepository.signInAnonymously(); return CustomRoutine<Account, Data?>(
routine: authenticationRepository.signInAnonymously,
// Custom routine attachedLogic: (routineResult) => onSignInAnonymously(
final customRoutineResult = await onSignInAnonymously(result, form); routineResult,
if (customRoutineResult.isErr) { form,
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<Data>(
account: account,
data: customRoutineResult.ok,
), ),
); onError: (error) {
emit(
authenticationRepository.addSession(signedInSession); SignInState(
form: form,
emit( errorMessage: error.message,
result.fold( status: FormStatus.submissionFailure,
(value) => ),
SignInState(form: form, status: FormStatus.submissionSuccess), );
(error) => SignInState( addError(error);
form: form, },
errorMessage: error.message, onSuccess: (account, data) {
status: FormStatus.submissionFailure, authenticationRepository.addSession(
), SessionWrapper(
), event: SignedInEvent(account: account),
); session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
} }
} }

View File

@ -18,12 +18,20 @@ import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart'; 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/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/domain/domain.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.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_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.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<Data> on BaseSignInCubit<Data> { mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in with email and password.
FutureOrResult<Data?> onSignInWithEmailAndPassword( FutureOrResult<Data?> onSignInWithEmailAndPassword(
Result<Account, AppException> result, Result<Account, AppException> result,
WyattForm form, WyattForm form,
@ -76,6 +84,7 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
dataChanged(AuthFormField.password, validator); dataChanged(AuthFormField.password, validator);
} }
/// {@macro signin_pwd}
FutureOr<void> signInWithEmailAndPassword() async { FutureOr<void> signInWithEmailAndPassword() async {
if (state.status.isSubmissionInProgress) { if (state.status.isSubmissionInProgress) {
return; return;
@ -106,63 +115,42 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
); );
} }
final result = await authenticationRepository.signInWithEmailAndPassword( return CustomRoutine<Account, Data?>(
email: email!, routine: () => authenticationRepository.signInWithEmailAndPassword(
password: password!, 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<Data>(
account: account,
data: customRoutineResult.ok,
), ),
); attachedLogic: (routineResult) => onSignInWithEmailAndPassword(
routineResult,
authenticationRepository.addSession(signedInSession); form,
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<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
} }
} }

View File

@ -17,17 +17,26 @@
import 'dart:async'; import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart'; 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/domain/entities/entities.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.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_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.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<Data> on BaseSignInCubit<Data> { mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in with google.
FutureOrResult<Data?> onSignInWithGoogle( FutureOrResult<Data?> onSignInWithGoogle(
Result<Account, AppException> result, Result<Account, AppException> result,
WyattForm form, WyattForm form,
); );
/// {@macro signin_google}
FutureOr<void> signInWithGoogle() async { FutureOr<void> signInWithGoogle() async {
if (state.status.isSubmissionInProgress) { if (state.status.isSubmissionInProgress) {
return; return;
@ -35,60 +44,39 @@ mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
final form = formRepository.accessForm(formName); final form = formRepository.accessForm(formName);
emit(SignInState(form: form, status: FormStatus.submissionInProgress)); emit(SignInState(form: form, status: FormStatus.submissionInProgress));
final result = await authenticationRepository.signInWithGoogle(); return CustomRoutine<Account, Data?>(
routine: authenticationRepository.signInWithGoogle,
// Custom routine attachedLogic: (routineResult) => onSignInWithGoogle(
final customRoutineResult = await onSignInWithGoogle( routineResult,
result, form,
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<Data>(
account: account,
data: customRoutineResult.ok,
), ),
); onError: (error) {
emit(
authenticationRepository.addSession(signedInSession); SignInState(
form: form,
emit( errorMessage: error.message,
result.fold( status: FormStatus.submissionFailure,
(value) => ),
SignInState(form: form, status: FormStatus.submissionSuccess), );
(error) => SignInState( addError(error);
form: form, },
errorMessage: error.message, onSuccess: (account, data) {
status: FormStatus.submissionFailure, authenticationRepository.addSession(
), SessionWrapper(
), event: SignedInEvent(account: account),
); session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
} }
} }

View File

@ -32,6 +32,8 @@ part 'base_sign_in_cubit.dart';
part 'sign_in_state.dart'; part 'sign_in_state.dart';
/// Fully featured sign in cubit. /// Fully featured sign in cubit.
///
/// Sufficient in most cases. (Where fine granularity is not required.)
class SignInCubit<Data> extends BaseSignInCubit<Data> class SignInCubit<Data> extends BaseSignInCubit<Data>
with with
SignInAnonymously<Data>, SignInAnonymously<Data>,

View File

@ -16,6 +16,7 @@
part of 'sign_in_cubit.dart'; part of 'sign_in_cubit.dart';
/// Sign in cubit state to manage the form.
class SignInState extends FormDataState { class SignInState extends FormDataState {
const SignInState({ const SignInState({
required super.form, required super.form,

View File

@ -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_authentication_bloc/src/features/sign_in/sign_in.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class SignInListener<Extra> extends StatelessWidget { /// Widget that listens and builds a child based on the state of
/// the sign in cubit
class SignInListener<Data> extends StatelessWidget {
const SignInListener({ const SignInListener({
required this.child, required this.child,
this.onProgress, this.onProgress,
@ -41,7 +43,7 @@ class SignInListener<Extra> extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) =>
BlocListener<SignInCubit<Extra>, SignInState>( BlocListener<SignInCubit<Data>, SignInState>(
listener: (context, state) { listener: (context, state) {
if (customBuilder != null) { if (customBuilder != null) {
return customBuilder!(context, state); return customBuilder!(context, state);

View File

@ -16,6 +16,8 @@
part of 'sign_up_cubit.dart'; 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<Data> extends FormDataCubit<SignUpState> { abstract class BaseSignUpCubit<Data> extends FormDataCubit<SignUpState> {
BaseSignUpCubit({ BaseSignUpCubit({
required this.authenticationRepository, required this.authenticationRepository,

View File

@ -17,13 +17,20 @@
import 'dart:async'; import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.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_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.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<Data> on BaseSignUpCubit<Data> { mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
/// This callback is triggered when a user creates an account.
///
/// For example: when a user sign up in firebase.
FutureOrResult<Data?> onSignUpWithEmailAndPassword( FutureOrResult<Data?> onSignUpWithEmailAndPassword(
Result<Account, AppException> result, Result<Account, AppException> result,
WyattForm form, WyattForm form,
@ -76,6 +83,7 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
dataChanged(AuthFormField.password, validator); dataChanged(AuthFormField.password, validator);
} }
/// {@macro signup_pwd}
FutureOr<void> signUpWithEmailPassword() async { FutureOr<void> signUpWithEmailPassword() async {
if (!state.status.isValidated) { if (!state.status.isValidated) {
return; return;
@ -97,65 +105,42 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
); );
} }
final result = await authenticationRepository.signUpWithEmailAndPassword( return CustomRoutine<Account, Data?>(
email: email!, routine: () => authenticationRepository.signUpWithEmailAndPassword(
password: password!, 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<Data>(
account: account,
data: customRoutineResult.ok,
), ),
); attachedLogic: (routineResult) => onSignUpWithEmailAndPassword(
routineResult,
authenticationRepository.addSession(signedUpSession); form,
emit(
result.fold(
(value) => SignUpState(
form: form,
status: FormStatus.submissionSuccess,
),
(error) => SignUpState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
), ),
); 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<Data>(
account: account,
data: data,
),
),
);
emit(
SignUpState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
} }
} }

View File

@ -28,6 +28,9 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'base_sign_up_cubit.dart'; part 'base_sign_up_cubit.dart';
part 'sign_up_state.dart'; part 'sign_up_state.dart';
/// Fully featured sign up cubit.
///
/// Sufficient in most cases. (Where fine granularity is not required.)
class SignUpCubit<Data> extends BaseSignUpCubit<Data> class SignUpCubit<Data> extends BaseSignUpCubit<Data>
with SignUpWithEmailPassword<Data> { with SignUpWithEmailPassword<Data> {
SignUpCubit({required super.authenticationRepository}); SignUpCubit({required super.authenticationRepository});

View File

@ -16,6 +16,7 @@
part of 'sign_up_cubit.dart'; part of 'sign_up_cubit.dart';
/// Sign up cubit state to manage the form.
class SignUpState extends FormDataState { class SignUpState extends FormDataState {
const SignUpState({ const SignUpState({
required super.form, required super.form,

View File

@ -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_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class SignUpListener<Extra> extends StatelessWidget { /// Widget that listens and builds a child based on the state of
/// the sign up cubit
class SignUpListener<Data> extends StatelessWidget {
const SignUpListener({ const SignUpListener({
required this.child, required this.child,
this.onProgress, this.onProgress,
@ -41,7 +43,7 @@ class SignUpListener<Extra> extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) =>
BlocListener<SignUpCubit<Extra>, SignUpState>( BlocListener<SignUpCubit<Data>, SignUpState>(
listener: (context, state) { listener: (context, state) {
if (customBuilder != null) { if (customBuilder != null) {
return customBuilder!(context, state); return customBuilder!(context, state);