feat(auth)!: add email verification, password reset support
This commit is contained in:
parent
1dd49fa080
commit
03a51b97ad
@ -17,4 +17,5 @@
|
||||
abstract class AuthFormName {
|
||||
static const String signUpForm = 'wyattSignUpForm';
|
||||
static const String signInForm = 'wyattSignInForm';
|
||||
static const String passwordResetForm = 'wyattPasswordResetForm';
|
||||
}
|
||||
|
@ -246,3 +246,10 @@ abstract class SignOutFailureInterface extends AuthenticationFailureInterface {
|
||||
/// {@macro sign_out_failure}
|
||||
SignOutFailureInterface.fromCode(super.code) : super.fromCode();
|
||||
}
|
||||
|
||||
abstract class GetIdTokenFailureInterface
|
||||
extends AuthenticationFailureInterface {
|
||||
GetIdTokenFailureInterface(super.code, super.msg);
|
||||
|
||||
GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode();
|
||||
}
|
||||
|
@ -270,3 +270,10 @@ class SignOutFailureFirebase extends SignOutFailureInterface {
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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_type_utils/wyatt_type_utils.dart';
|
||||
|
||||
@ -28,7 +27,18 @@ class AuthenticationFirebaseDataSourceImpl
|
||||
|
||||
Account _mapper(User user) => AccountModel(
|
||||
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,
|
||||
phoneNumber: user.phoneNumber,
|
||||
photoURL: user.photoURL,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -55,6 +65,8 @@ class AuthenticationFirebaseDataSourceImpl
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
/// {@macro signup}
|
||||
Future<Account> signUp({
|
||||
required String email,
|
||||
required String password,
|
||||
@ -95,9 +107,10 @@ class AuthenticationFirebaseDataSourceImpl
|
||||
} else {
|
||||
throw Exception(); // Get caught just after.
|
||||
}
|
||||
} on FirebaseAuthException catch (e) {
|
||||
throw GetIdTokenFailureFirebase.fromCode(e.code);
|
||||
} catch (_) {
|
||||
// TODO(hpcl): implement a non ambiguous exception for this case
|
||||
throw ServerException();
|
||||
throw GetIdTokenFailureFirebase();
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,4 +120,83 @@ class AuthenticationFirebaseDataSourceImpl
|
||||
final Account? account = (user.isNotNull) ? _mapper(user!) : null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
// Copyright (C) 2022 WYATT GROUP
|
||||
// Please see the AUTHORS file for details.
|
||||
//
|
||||
@ -16,12 +17,47 @@
|
||||
|
||||
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
|
||||
|
||||
class AccountModel implements Account {
|
||||
class AccountModel extends Account {
|
||||
@override
|
||||
final String uid;
|
||||
|
||||
@override
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
@ -208,4 +208,70 @@ class AuthenticationRepositoryImpl<T extends Object>
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
@ -30,7 +30,22 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource {
|
||||
|
||||
Future<void> signOut();
|
||||
|
||||
Future<void> refresh();
|
||||
|
||||
Stream<Account?> streamAccount();
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -14,9 +14,10 @@
|
||||
// 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:equatable/equatable.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.
|
||||
String get uid;
|
||||
|
||||
@ -24,4 +25,52 @@ abstract class Account extends Entity {
|
||||
///
|
||||
/// Will be `null` if signing in anonymously.
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
@ -14,10 +14,14 @@
|
||||
// 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:equatable/equatable.dart';
|
||||
import 'package:wyatt_architecture/wyatt_architecture.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;
|
||||
T? get data;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [account, data];
|
||||
}
|
||||
|
@ -22,18 +22,83 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||
abstract class AuthenticationRepository<T> extends BaseRepository {
|
||||
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({
|
||||
required String email,
|
||||
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({
|
||||
required String email,
|
||||
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> 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();
|
||||
|
||||
FutureResult<String> getIdentityToken();
|
||||
|
@ -40,16 +40,16 @@ class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
|
||||
accountFutureResult.fold(
|
||||
(value) {
|
||||
if (value.account.isNotNull) {
|
||||
emit(AuthenticationState.authenticated(value));
|
||||
emit(AuthenticationState<Extra>.authenticated(value));
|
||||
return;
|
||||
}
|
||||
_authenticationRepository.destroyCache();
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
emit(AuthenticationState<Extra>.unauthenticated());
|
||||
return;
|
||||
},
|
||||
(error) {
|
||||
_authenticationRepository.destroyCache();
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
emit(AuthenticationState<Extra>.unauthenticated());
|
||||
return;
|
||||
},
|
||||
);
|
||||
|
@ -35,7 +35,7 @@ class AuthenticationState<Extra> extends Equatable {
|
||||
: this._(status: AuthenticationStatus.unauthenticated);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
List<Object?> get props => [status, accountWrapper];
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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)';
|
||||
}
|
@ -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';
|
@ -15,5 +15,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
export 'authentication/authentication.dart';
|
||||
export 'email_verification/email_verification.dart';
|
||||
export 'password_reset/password_reset.dart';
|
||||
export 'sign_in/sign_in.dart';
|
||||
export 'sign_up/sign_up.dart';
|
||||
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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)';
|
||||
}
|
@ -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';
|
@ -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,
|
||||
);
|
||||
}
|
@ -15,3 +15,4 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
export 'cubit/sign_in_cubit.dart';
|
||||
export 'listener/sign_in_listener.dart';
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
@ -15,3 +15,4 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
export 'cubit/sign_up_cubit.dart';
|
||||
export 'listener/sign_up_listener.dart';
|
||||
|
Loading…
x
Reference in New Issue
Block a user