feat(auth)!: add email verification, password reset support

This commit is contained in:
Hugo Pointcheval 2022-11-11 16:53:36 -05:00
parent 1dd49fa080
commit 03a51b97ad
Signed by: hugo
GPG Key ID: A9E8E9615379254F
24 changed files with 907 additions and 20 deletions

View File

@ -17,4 +17,5 @@
abstract class AuthFormName { abstract class AuthFormName {
static const String signUpForm = 'wyattSignUpForm'; static const String signUpForm = 'wyattSignUpForm';
static const String signInForm = 'wyattSignInForm'; static const String signInForm = 'wyattSignInForm';
static const String passwordResetForm = 'wyattPasswordResetForm';
} }

View File

@ -246,3 +246,10 @@ abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
/// {@macro sign_out_failure} /// {@macro sign_out_failure}
SignOutFailureInterface.fromCode(super.code) : super.fromCode(); SignOutFailureInterface.fromCode(super.code) : super.fromCode();
} }
abstract class GetIdTokenFailureInterface
extends AuthenticationFailureInterface {
GetIdTokenFailureInterface(super.code, super.msg);
GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode();
}

View File

@ -270,3 +270,10 @@ class SignOutFailureFirebase extends SignOutFailureInterface {
SignOutFailureFirebase.fromCode(super.code) : super.fromCode(); SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
} }
class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface {
GetIdTokenFailureFirebase([String? code, String? msg])
: super(code ?? 'unknown', msg ?? 'An unknown error occurred.');
GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode();
}

View File

@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@ -28,7 +27,18 @@ class AuthenticationFirebaseDataSourceImpl
Account _mapper(User user) => AccountModel( Account _mapper(User user) => AccountModel(
uid: user.uid, uid: user.uid,
emailVerified: user.emailVerified,
isAnonymous: user.isAnonymous,
providerId: user.providerData.first.providerId,
creationTime: user.metadata.creationTime,
lastSignInTime: user.metadata.lastSignInTime,
isNewUser: (user.metadata.creationTime != null &&
user.metadata.lastSignInTime != null)
? user.metadata.lastSignInTime! == user.metadata.creationTime!
: null,
email: user.email, email: user.email,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
); );
@override @override
@ -55,6 +65,8 @@ class AuthenticationFirebaseDataSourceImpl
} }
@override @override
/// {@macro signup}
Future<Account> signUp({ Future<Account> signUp({
required String email, required String email,
required String password, required String password,
@ -95,9 +107,10 @@ class AuthenticationFirebaseDataSourceImpl
} else { } else {
throw Exception(); // Get caught just after. throw Exception(); // Get caught just after.
} }
} on FirebaseAuthException catch (e) {
throw GetIdTokenFailureFirebase.fromCode(e.code);
} catch (_) { } catch (_) {
// TODO(hpcl): implement a non ambiguous exception for this case throw GetIdTokenFailureFirebase();
throw ServerException();
} }
} }
@ -107,4 +120,83 @@ class AuthenticationFirebaseDataSourceImpl
final Account? account = (user.isNotNull) ? _mapper(user!) : null; final Account? account = (user.isNotNull) ? _mapper(user!) : null;
return account; return account;
}); });
@override
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
}) async {
try {
await _firebaseAuth.confirmPasswordReset(
code: code,
newPassword: newPassword,
);
} on FirebaseAuthException catch (e) {
throw ConfirmPasswordResetFailureFirebase.fromCode(e.code);
} catch (_) {
throw ConfirmPasswordResetFailureFirebase();
}
}
@override
Future<void> sendEmailVerification() async {
try {
await _firebaseAuth.currentUser!.sendEmailVerification();
} on FirebaseAuthException catch (e) {
throw SendEmailVerificationFailureFirebase.fromCode(e.code);
} catch (_) {
throw SendEmailVerificationFailureFirebase();
}
}
@override
Future<void> sendPasswordResetEmail({required String email}) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
throw SendPasswordResetEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw SendPasswordResetEmailFailureFirebase();
}
}
@override
Future<Account> signInAnonymously() async {
try {
final userCredential = await _firebaseAuth.signInAnonymously();
final user = userCredential.user;
if (user.isNotNull) {
return _mapper(user!);
} else {
throw Exception(); // Get caught just after.
}
} on FirebaseAuthException catch (e) {
throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInAnonymouslyFailureFirebase();
}
}
@override
Future<bool> verifyPasswordResetCode({required String code}) async {
try {
final email = await _firebaseAuth.verifyPasswordResetCode(code);
return email.isNotNullOrEmpty;
} on FirebaseAuthException catch (e) {
throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code);
} catch (_) {
throw VerifyPasswordResetCodeFailureFirebase();
}
}
@override
Future<void> refresh() async {
try {
await _firebaseAuth.currentUser!.reload();
} on FirebaseAuthException catch (e) {
throw RefreshFailureFirebase.fromCode(e.code);
} catch (_) {
throw RefreshFailureFirebase();
}
}
} }

View File

@ -1,3 +1,4 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP // Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details. // Please see the AUTHORS file for details.
// //
@ -16,12 +17,47 @@
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
class AccountModel implements Account { class AccountModel extends Account {
@override @override
final String uid; final String uid;
@override @override
final String? email; final String? email;
AccountModel({required this.uid, required this.email}); @override
final DateTime? creationTime;
@override
final bool emailVerified;
@override
final bool isAnonymous;
@override
final bool? isNewUser;
@override
final DateTime? lastSignInTime;
@override
final String? phoneNumber;
@override
final String? photoURL;
@override
final String providerId;
AccountModel({
required this.uid,
required this.emailVerified,
required this.isAnonymous,
required this.providerId,
this.lastSignInTime,
this.creationTime,
this.isNewUser,
this.email,
this.phoneNumber,
this.photoURL,
});
} }

View File

@ -208,4 +208,70 @@ class AuthenticationRepositoryImpl<T extends Object>
AccountWrapperModel<T>(account, null), AccountWrapperModel<T>(account, null),
); );
}); });
@override
FutureResult<void> confirmPasswordReset({
required String code,
required String newPassword,
}) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.confirmPasswordReset(
code: code,
newPassword: newPassword,
);
},
(error) => error,
);
@override
FutureResult<void> sendEmailVerification() =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.sendEmailVerification();
},
(error) => error,
);
@override
FutureResult<void> sendPasswordResetEmail({required String email}) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.sendPasswordResetEmail(
email: email,
);
},
(error) => error,
);
@override
FutureResult<Account> signInAnonymously() =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account =
await _authenticationRemoteDataSource.signInAnonymously();
return account;
},
(error) => error,
);
@override
FutureResult<bool> verifyPasswordResetCode({required String code}) =>
Result.tryCatchAsync<bool, AppException, AppException>(
() async {
final response = await _authenticationRemoteDataSource
.verifyPasswordResetCode(code: code);
return response;
},
(error) => error,
);
@override
FutureResult<void> refresh() =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.refresh();
},
(error) => error,
);
} }

View File

@ -30,7 +30,22 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
Future<void> signOut(); Future<void> signOut();
Future<void> refresh();
Stream<Account?> streamAccount(); Stream<Account?> streamAccount();
Future<String> getIdentityToken(); Future<String> getIdentityToken();
Future<void> sendEmailVerification();
Future<void> sendPasswordResetEmail({required String email});
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
});
Future<bool> verifyPasswordResetCode({required String code});
Future<Account> signInAnonymously();
} }

View File

@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
abstract class Account extends Entity { abstract class Account extends Equatable implements Entity {
/// The user's unique ID. /// The user's unique ID.
String get uid; String get uid;
@ -24,4 +25,52 @@ abstract class Account extends Entity {
/// ///
/// Will be `null` if signing in anonymously. /// Will be `null` if signing in anonymously.
String? get email; String? get email;
/// Returns whether the users email address has been verified.
///
/// To send a verification email, see `SendEmailVerification`.
bool get emailVerified;
/// Returns whether the user is a anonymous.
bool get isAnonymous;
/// Returns the users account creation time.
///
/// When this account was created as dictated by the server clock.
DateTime? get creationTime;
/// When the user last signed in as dictated by the server clock.
DateTime? get lastSignInTime;
/// Returns the users phone number.
///
/// This property will be `null` if the user has not signed in or been has
/// their phone number linked.
String? get phoneNumber;
/// Returns a photo URL for the user.
///
/// This property will be populated if the user has signed in or been linked
/// with a 3rd party OAuth provider (such as Google).
String? get photoURL;
/// The provider ID for the user.
String get providerId;
/// Whether the user account has been recently created.
bool? get isNewUser;
@override
List<Object?> get props => [
uid,
email,
emailVerified,
isAnonymous,
creationTime,
lastSignInTime,
phoneNumber,
photoURL,
providerId,
isNewUser,
];
} }

View File

@ -14,10 +14,14 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
abstract class AccountWrapper<T> extends Entity { abstract class AccountWrapper<T> extends Equatable implements Entity {
Account? get account; Account? get account;
T? get data; T? get data;
@override
List<Object?> get props => [account, data];
} }

View File

@ -22,18 +22,83 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
abstract class AuthenticationRepository<T> extends BaseRepository { abstract class AuthenticationRepository<T> extends BaseRepository {
FormRepository get formRepository; FormRepository get formRepository;
/// {@template signup}
/// Creates a new user with the provided [email] and [password].
///
/// Returns the newly created user's unique identifier.
///
/// Throws a SignUpWithEmailAndPasswordFailureInterface if
/// an exception occurs.
/// {@endtemplate}
FutureResult<Account> signUp({ FutureResult<Account> signUp({
required String email, required String email,
required String password, required String password,
}); });
/// {@template send_email_verification}
/// Sends verification email to the account email.
///
/// Throws a SendEmailVerificationFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<void> sendEmailVerification();
/// {@template send_password_reset_email}
/// Sends a password reset email to the provided [email].
///
/// Throws a SendPasswordResetEmailFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<void> sendPasswordResetEmail({required String email});
/// {@template confirm_password_reset}
/// Confirms the password reset with the provided [newPassword] and [code].
///
/// Throws a ConfirmPasswordResetFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<void> confirmPasswordReset({
required String code,
required String newPassword,
});
/// {@template verify_password_reset_code}
/// Verify password reset code.
///
/// Throws a VerifyPasswordResetCodeFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<bool> verifyPasswordResetCode({required String code});
/// {@template signin_anom}
/// Sign in anonymously.
///
/// Throws a SignInAnonymouslyFailureInterface if an exception occurs.
/// {@endtemplate}
FutureResult<Account> signInAnonymously();
/// {@template signin_pwd}
/// Signs in with the provided [email] and [password].
///
/// Throws a SignInWithEmailAndPasswordFailureInterface if
/// an exception occurs.
/// {@endtemplate}
FutureResult<Account> signInWithEmailAndPassword({ FutureResult<Account> signInWithEmailAndPassword({
required String email, required String email,
required String password, required String password,
}); });
/// {@template signout}
/// Signs out the current user.
/// It also clears the cache and the associated data.
/// {@endtemplate}
FutureResult<void> signOut(); FutureResult<void> signOut();
FutureResult<void> refresh();
/// {@template stream_account}
/// Stream of [AccountWrapper] which will emit the current account when
/// the authentication state changes.
///
/// Emits [AccountWrapper] with null [Account] if the user is not
/// authenticated.
/// {@endtemplate}
Stream<FutureResult<AccountWrapper<T>>> streamAccount(); Stream<FutureResult<AccountWrapper<T>>> streamAccount();
FutureResult<String> getIdentityToken(); FutureResult<String> getIdentityToken();

View File

@ -40,16 +40,16 @@ class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
accountFutureResult.fold( accountFutureResult.fold(
(value) { (value) {
if (value.account.isNotNull) { if (value.account.isNotNull) {
emit(AuthenticationState.authenticated(value)); emit(AuthenticationState<Extra>.authenticated(value));
return; return;
} }
_authenticationRepository.destroyCache(); _authenticationRepository.destroyCache();
emit(const AuthenticationState.unauthenticated()); emit(AuthenticationState<Extra>.unauthenticated());
return; return;
}, },
(error) { (error) {
_authenticationRepository.destroyCache(); _authenticationRepository.destroyCache();
emit(const AuthenticationState.unauthenticated()); emit(AuthenticationState<Extra>.unauthenticated());
return; return;
}, },
); );

View File

@ -35,7 +35,7 @@ class AuthenticationState<Extra> extends Equatable {
: this._(status: AuthenticationStatus.unauthenticated); : this._(status: AuthenticationStatus.unauthenticated);
@override @override
List<Object?> get props => [status]; List<Object?> get props => [status, accountWrapper];
@override @override
String toString() => String toString() =>

View File

@ -0,0 +1,65 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/email_verification/cubit/email_verification_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class EmailVerificationBuilder<Extra> extends StatelessWidget {
const EmailVerificationBuilder({
required this.verified,
required this.notVerified,
required this.onError,
this.customBuilder,
super.key,
});
final Widget Function(BuildContext context) verified;
final Widget Function(BuildContext context) notVerified;
final Widget Function(
BuildContext context,
FormStatus status,
String? errorMessage,
) onError;
final Widget Function(BuildContext context, EmailVerificationState)?
customBuilder;
@override
Widget build(BuildContext context) =>
BlocBuilder<EmailVerificationCubit<Extra>, EmailVerificationState>(
builder: (context, state) {
if (customBuilder != null) {
return customBuilder!(context, state);
}
if (state.status.isSubmissionFailure ||
state.status.isSubmissionCanceled) {
return onError(
context,
state.status,
state.errorMessage,
);
}
if (state.isVerified) {
return verified(context);
}
return notVerified(context);
},
);
}

View File

@ -0,0 +1,77 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/repositories.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'email_verification_state.dart';
class EmailVerificationCubit<Extra> extends Cubit<EmailVerificationState> {
final AuthenticationRepository<Extra> _authenticationRepository;
EmailVerificationCubit({
required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
super(const EmailVerificationState());
FutureOr<void> sendEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress));
final response = await _authenticationRepository.sendEmailVerification();
emit(
response.fold(
(value) => state.copyWith(status: FormStatus.submissionSuccess),
(error) => state.copyWith(
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
),
);
}
FutureOr<void> checkEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress));
final refresh = await _authenticationRepository.refresh();
if (refresh.isErr) {
final refreshError = refresh.err!;
emit(
EmailVerificationState(
errorMessage: refreshError.message,
status: FormStatus.submissionFailure,
),
);
return;
}
final currentAccount = await _authenticationRepository.getAccount();
emit(
currentAccount.fold(
(value) => state.copyWith(
isVerified: value.emailVerified,
status: FormStatus.submissionSuccess,
),
(error) => EmailVerificationState(
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
),
);
}
}

View File

@ -0,0 +1,49 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'email_verification_cubit.dart';
class EmailVerificationState extends Equatable {
final FormStatus status;
final bool isVerified;
final String? errorMessage;
const EmailVerificationState({
this.isVerified = false,
this.status = FormStatus.pure,
this.errorMessage,
});
EmailVerificationState copyWith({
FormStatus? status,
bool? isVerified,
String? errorMessage,
}) =>
EmailVerificationState(
status: status ?? this.status,
isVerified: isVerified ?? this.isVerified,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
List<Object?> get props => [status, isVerified, errorMessage];
@override
String toString() => 'EmailVerificationState(status: ${status.name} '
'${(errorMessage != null) ? " [$errorMessage]" : ""}, '
'isVerified: $isVerified)';
}

View File

@ -0,0 +1,18 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'builder/email_verification_builder.dart';
export 'cubit/email_verification_cubit.dart';

View File

@ -1,19 +1,21 @@
// Copyright (C) 2022 WYATT GROUP // Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details. // Please see the AUTHORS file for details.
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// any later version. // any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication/authentication.dart'; export 'authentication/authentication.dart';
export 'email_verification/email_verification.dart';
export 'password_reset/password_reset.dart';
export 'sign_in/sign_in.dart'; export 'sign_in/sign_in.dart';
export 'sign_up/sign_up.dart'; export 'sign_up/sign_up.dart';

View File

@ -0,0 +1,138 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'password_reset_state.dart';
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
PasswordResetCubit({
required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
super(
PasswordResetState(
form: authenticationRepository.formRepository
.accessForm(AuthFormName.passwordResetForm),
),
);
@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,
FormInputValidator<Value, ValidationError> dirtyValue,
) {
final form = _formRepository.accessForm(formName).clone();
try {
form.updateValidator(key, dirtyValue);
_formRepository.updateForm(form);
} catch (e) {
rethrow;
}
emit(
state.copyWith(form: form, status: form.validate()),
);
}
@override
FutureOr<void> reset() {
final form = state.form.reset();
_formRepository.updateForm(form);
emit(
state.copyWith(form: form, status: form.validate()),
);
}
@override
FutureOr<void> submit() async {
if (!state.status.isValidated) {
return;
}
emit(state.copyWith(status: FormStatus.submissionInProgress));
final form = _formRepository.accessForm(formName);
final email = form.valueOf<String?>(AuthFormField.email);
if (email.isNullOrEmpty) {
emit(
state.copyWith(
errorMessage: 'An error occured while retrieving data from the form.',
status: FormStatus.submissionFailure,
),
);
}
final response = await _authenticationRepository.sendPasswordResetEmail(
email: email!,
);
emit(
response.fold(
(value) => state.copyWith(status: FormStatus.submissionSuccess),
(error) => state.copyWith(
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
),
);
}
@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(),
),
);
}
}

View File

@ -0,0 +1,45 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'password_reset_cubit.dart';
class PasswordResetState extends FormDataState {
Email get email => form.validatorOf(AuthFormField.email);
const PasswordResetState({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
PasswordResetState copyWith({
WyattForm? form,
FormStatus? status,
String? errorMessage,
}) =>
PasswordResetState(
form: form ?? this.form,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
List<Object> get props => [email, status];
@override
String toString() => 'PasswordResetState(status: ${status.name} '
'${(errorMessage != null) ? " [$errorMessage]" : ""}, $form)';
}

View File

@ -0,0 +1,17 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'cubit/password_reset_cubit.dart';

View File

@ -0,0 +1,66 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
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 {
const SignInListener({
required this.child,
this.onProgress,
this.onSuccess,
this.onError,
this.customBuilder,
super.key,
});
final void Function(BuildContext context)? onProgress;
final void Function(BuildContext context)? onSuccess;
final void Function(
BuildContext context,
FormStatus status,
String? errorMessage,
)? onError;
final void Function(BuildContext context, SignInState state)? customBuilder;
final Widget child;
@override
Widget build(BuildContext context) =>
BlocListener<SignInCubit<Extra>, SignInState>(
listener: (context, state) {
if (customBuilder != null) {
return customBuilder!(context, state);
}
if (onSuccess != null &&
state.status == FormStatus.submissionSuccess) {
return onSuccess!(context);
}
if (onProgress != null &&
state.status == FormStatus.submissionInProgress) {
return onProgress!(context);
}
if (onError != null &&
(state.status == FormStatus.submissionCanceled ||
state.status == FormStatus.submissionFailure)) {
return onError!(context, state.status, state.errorMessage);
}
},
child: child,
);
}

View File

@ -1,17 +1,18 @@
// Copyright (C) 2022 WYATT GROUP // Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details. // Please see the AUTHORS file for details.
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// any later version. // any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'cubit/sign_in_cubit.dart'; export 'cubit/sign_in_cubit.dart';
export 'listener/sign_in_listener.dart';

View File

@ -0,0 +1,66 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
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 {
const SignUpListener({
required this.child,
this.onProgress,
this.onSuccess,
this.onError,
this.customBuilder,
super.key,
});
final void Function(BuildContext context)? onProgress;
final void Function(BuildContext context)? onSuccess;
final void Function(
BuildContext context,
FormStatus status,
String? errorMessage,
)? onError;
final void Function(BuildContext context, SignUpState state)? customBuilder;
final Widget child;
@override
Widget build(BuildContext context) =>
BlocListener<SignUpCubit<Extra>, SignUpState>(
listener: (context, state) {
if (customBuilder != null) {
return customBuilder!(context, state);
}
if (onSuccess != null &&
state.status == FormStatus.submissionSuccess) {
return onSuccess!(context);
}
if (onProgress != null &&
state.status == FormStatus.submissionInProgress) {
return onProgress!(context);
}
if (onError != null &&
(state.status == FormStatus.submissionCanceled ||
state.status == FormStatus.submissionFailure)) {
return onError!(context, state.status, state.errorMessage);
}
},
child: child,
);
}

View File

@ -1,17 +1,18 @@
// Copyright (C) 2022 WYATT GROUP // Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details. // Please see the AUTHORS file for details.
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// any later version. // any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'cubit/sign_up_cubit.dart'; export 'cubit/sign_up_cubit.dart';
export 'listener/sign_up_listener.dart';