feat(authentication): add custom routine, and documentation
This commit is contained in:
parent
eafe4dc7c6
commit
3faceeebb6
@ -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(),
|
||||
),
|
||||
),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
@ -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<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 {
|
||||
final AuthenticationRepository<int> authenticationRepository =
|
||||
AuthenticationRepositoryImpl(
|
||||
@ -64,7 +51,9 @@ class App extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
AuthenticationState<int>? previous;
|
||||
|
||||
final AuthenticationCubit<int> authenticationCubit = ExampleAuthenticationCubit(authenticationRepository: authenticationRepository);
|
||||
final AuthenticationCubit<int> authenticationCubit =
|
||||
ExampleAuthenticationCubit(
|
||||
authenticationRepository: authenticationRepository);
|
||||
|
||||
final GoRouter router = GoRouter(
|
||||
initialLocation: '/',
|
||||
@ -91,7 +80,7 @@ class App extends StatelessWidget {
|
||||
return '/home';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return state.name;
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -49,4 +49,11 @@ class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
|
||||
|
||||
return const Ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOrResult<void> onDelete() {
|
||||
print('onDelete');
|
||||
|
||||
return const Ok(null);
|
||||
}
|
||||
}
|
||||
|
@ -35,14 +35,20 @@ class SubPage extends StatelessWidget {
|
||||
icon: const Icon(Icons.logout_rounded)),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
context.read<AuthenticationRepository<int>>().refresh(),
|
||||
context.read<AuthenticationCubit<int>>().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<AuthenticationCubit<int>>().delete(),
|
||||
child: const Text('Delete account'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -35,7 +35,7 @@ class CustomRoutine<R, Data> {
|
||||
final FutureOr<Result<Data?, AppException>> Function(
|
||||
Result<R, AppException> routineResult,
|
||||
) attachedLogic;
|
||||
final void Function(AppException? exception) onError;
|
||||
final void Function(AppException exception) onError;
|
||||
final void Function(R result, Data? data) onSuccess;
|
||||
|
||||
FutureOr<void> call() async {
|
||||
@ -46,17 +46,17 @@ class CustomRoutine<R, Data> {
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ abstract class AuthenticationCubit<Data>
|
||||
}
|
||||
final AuthenticationRepository<Data> authenticationRepository;
|
||||
|
||||
SessionWrapper<Data>? latestSession;
|
||||
SessionWrapper<Data>? _latestSession;
|
||||
|
||||
void _listenForAuthenticationChanges() {
|
||||
authenticationRepository.sessionStream().asyncMap((wrapper) async {
|
||||
@ -67,7 +67,7 @@ abstract class AuthenticationCubit<Data>
|
||||
}
|
||||
return wrapper;
|
||||
}).listen((wrapper) async {
|
||||
latestSession = wrapper;
|
||||
_latestSession = wrapper;
|
||||
final session = wrapper.session;
|
||||
if (session != null) {
|
||||
emit(AuthenticationState<Data>.authenticated(wrapper));
|
||||
@ -79,143 +79,59 @@ abstract class AuthenticationCubit<Data>
|
||||
}
|
||||
|
||||
/// {@macro refresh}
|
||||
FutureOr<void> 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<Data>(
|
||||
account: account,
|
||||
data: sessionData,
|
||||
FutureOr<void> refresh() async => CustomRoutine<Account, Data?>(
|
||||
routine: authenticationRepository.refresh,
|
||||
attachedLogic: onRefresh,
|
||||
onError: addError,
|
||||
onSuccess: (result, data) => authenticationRepository.addSession(
|
||||
SessionWrapper(
|
||||
event: RefreshedEvent(account: result),
|
||||
session: Session<Data>(
|
||||
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<void> 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<Data>(
|
||||
account: account,
|
||||
data: sessionData,
|
||||
FutureOr<void> reauthenticate() async => CustomRoutine<Account, Data?>(
|
||||
routine: authenticationRepository.reauthenticate,
|
||||
attachedLogic: onReauthenticate,
|
||||
onError: addError,
|
||||
onSuccess: (result, data) => authenticationRepository.addSession(
|
||||
SessionWrapper(
|
||||
event: ReauthenticatedEvent(account: result),
|
||||
session: Session<Data>(
|
||||
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<void> signOut() async {
|
||||
final customRoutine = CustomRoutine<void, void>(
|
||||
// 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<void> signOut() async => CustomRoutine<void, void>(
|
||||
routine: authenticationRepository.signOut,
|
||||
attachedLogic: (routineResult) => onSignOut(),
|
||||
onError: addError,
|
||||
onSuccess: (result, data) => authenticationRepository
|
||||
.addSession(SessionWrapper<Data>(event: const SignedOutEvent())),
|
||||
).call();
|
||||
|
||||
/// {@macro delete}
|
||||
FutureOr<void> 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<void> delete() async => CustomRoutine<void, void>(
|
||||
routine: authenticationRepository.delete,
|
||||
attachedLogic: (routineResult) => onDelete(),
|
||||
onError: addError,
|
||||
onSuccess: (result, data) => authenticationRepository
|
||||
.addSession(SessionWrapper<Data>(event: const DeletedEvent())),
|
||||
).call();
|
||||
|
||||
/// Returns latest session wrapper.
|
||||
///
|
||||
/// 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
|
||||
/// the cache.
|
||||
|
@ -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<Extra> extends FormDataCubit<PasswordResetState> {
|
||||
PasswordResetCubit({
|
||||
required this.authenticationRepository,
|
||||
}) :
|
||||
super(
|
||||
}) : super(
|
||||
PasswordResetState(
|
||||
form: authenticationRepository.formRepository
|
||||
.accessForm(AuthFormName.passwordResetForm),
|
||||
),
|
||||
);
|
||||
final AuthenticationRepository<Extra> 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<void> dataChanged<Value>(
|
||||
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
|
||||
FutureOr<void> submit() async {
|
||||
if (!state.status.isValidated) {
|
||||
@ -109,29 +130,29 @@ 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(),
|
||||
),
|
||||
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<void> 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<String?, ValidationError>>(
|
||||
Validator validator,
|
||||
) {
|
||||
dataChanged(AuthFormField.email, validator);
|
||||
}
|
||||
|
||||
// TODO(wyatt): create base_password_reset_cubit and create mixins
|
||||
}
|
||||
|
@ -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<Data> extends FormDataCubit<SignInState> {
|
||||
BaseSignInCubit({
|
||||
required this.authenticationRepository,
|
||||
|
@ -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<Data> on BaseSignInCubit<Data> {
|
||||
/// This callback is triggered when a user signs in anonymously.
|
||||
FutureOrResult<Data?> onSignInAnonymously(
|
||||
Result<Account, AppException> result,
|
||||
WyattForm form,
|
||||
);
|
||||
|
||||
/// {@macro signin_anom}
|
||||
FutureOr<void> signInAnonymously() async {
|
||||
if (state.status.isSubmissionInProgress) {
|
||||
return;
|
||||
@ -36,57 +45,39 @@ mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: customRoutineResult.ok,
|
||||
return CustomRoutine<Account, Data?>(
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(
|
||||
SignInState(
|
||||
form: form,
|
||||
status: FormStatus.submissionSuccess,
|
||||
),
|
||||
);
|
||||
},
|
||||
).call();
|
||||
}
|
||||
}
|
||||
|
@ -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<Data> on BaseSignInCubit<Data> {
|
||||
/// This callback is triggered when a user signs in with email and password.
|
||||
FutureOrResult<Data?> onSignInWithEmailAndPassword(
|
||||
Result<Account, AppException> result,
|
||||
WyattForm form,
|
||||
@ -76,6 +84,7 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
|
||||
dataChanged(AuthFormField.password, validator);
|
||||
}
|
||||
|
||||
/// {@macro signin_pwd}
|
||||
FutureOr<void> signInWithEmailAndPassword() async {
|
||||
if (state.status.isSubmissionInProgress) {
|
||||
return;
|
||||
@ -106,63 +115,42 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
|
||||
);
|
||||
}
|
||||
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: customRoutineResult.ok,
|
||||
return CustomRoutine<Account, Data?>(
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(
|
||||
SignInState(
|
||||
form: form,
|
||||
status: FormStatus.submissionSuccess,
|
||||
),
|
||||
);
|
||||
},
|
||||
).call();
|
||||
}
|
||||
}
|
||||
|
@ -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<Data> on BaseSignInCubit<Data> {
|
||||
/// This callback is triggered when a user signs in with google.
|
||||
FutureOrResult<Data?> onSignInWithGoogle(
|
||||
Result<Account, AppException> result,
|
||||
WyattForm form,
|
||||
);
|
||||
|
||||
/// {@macro signin_google}
|
||||
FutureOr<void> signInWithGoogle() async {
|
||||
if (state.status.isSubmissionInProgress) {
|
||||
return;
|
||||
@ -35,60 +44,39 @@ mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: customRoutineResult.ok,
|
||||
return CustomRoutine<Account, Data?>(
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(
|
||||
SignInState(
|
||||
form: form,
|
||||
status: FormStatus.submissionSuccess,
|
||||
),
|
||||
);
|
||||
},
|
||||
).call();
|
||||
}
|
||||
}
|
||||
|
@ -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<Data> extends BaseSignInCubit<Data>
|
||||
with
|
||||
SignInAnonymously<Data>,
|
||||
|
@ -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,
|
||||
|
@ -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<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({
|
||||
required this.child,
|
||||
this.onProgress,
|
||||
@ -41,7 +43,7 @@ class SignInListener<Extra> extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
BlocListener<SignInCubit<Extra>, SignInState>(
|
||||
BlocListener<SignInCubit<Data>, SignInState>(
|
||||
listener: (context, state) {
|
||||
if (customBuilder != null) {
|
||||
return customBuilder!(context, state);
|
||||
|
@ -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<Data> extends FormDataCubit<SignUpState> {
|
||||
BaseSignUpCubit({
|
||||
required this.authenticationRepository,
|
||||
|
@ -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<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(
|
||||
Result<Account, AppException> result,
|
||||
WyattForm form,
|
||||
@ -76,6 +83,7 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
|
||||
dataChanged(AuthFormField.password, validator);
|
||||
}
|
||||
|
||||
/// {@macro signup_pwd}
|
||||
FutureOr<void> signUpWithEmailPassword() async {
|
||||
if (!state.status.isValidated) {
|
||||
return;
|
||||
@ -97,65 +105,42 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
|
||||
);
|
||||
}
|
||||
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: customRoutineResult.ok,
|
||||
return CustomRoutine<Account, Data?>(
|
||||
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<Data>(
|
||||
account: account,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(
|
||||
SignUpState(
|
||||
form: form,
|
||||
status: FormStatus.submissionSuccess,
|
||||
),
|
||||
);
|
||||
},
|
||||
).call();
|
||||
}
|
||||
}
|
||||
|
@ -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<Data> extends BaseSignUpCubit<Data>
|
||||
with SignUpWithEmailPassword<Data> {
|
||||
SignUpCubit({required super.authenticationRepository});
|
||||
|
@ -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,
|
||||
|
@ -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<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({
|
||||
required this.child,
|
||||
this.onProgress,
|
||||
@ -41,7 +43,7 @@ class SignUpListener<Extra> extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
BlocListener<SignUpCubit<Extra>, SignUpState>(
|
||||
BlocListener<SignUpCubit<Data>, SignUpState>(
|
||||
listener: (context, state) {
|
||||
if (customBuilder != null) {
|
||||
return customBuilder!(context, state);
|
||||
|
Loading…
x
Reference in New Issue
Block a user