feat(auth): add reactive repo + extra data + router examples + tests

This commit is contained in:
2022-08-26 13:42:53 +02:00
parent a1e36f84be
commit d9d45db5c0
75 changed files with 1673 additions and 434 deletions
@@ -1,76 +0,0 @@
// 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:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
part 'authentication_state.dart';
class AuthenticationCubit extends Cubit<AuthenticationState> {
final AuthenticationRepositoryInterface _authenticationRepository;
StreamSubscription<UserInterface>? _userSubscription;
final Future<Map<String, dynamic>> Function(UserInterface user)?
_onAuthSuccess;
AuthenticationCubit({
required AuthenticationRepositoryInterface authenticationRepository,
Future<Map<String, dynamic>> Function(UserInterface user)? onAuthSuccess,
}) : _authenticationRepository = authenticationRepository,
_onAuthSuccess = onAuthSuccess,
super(const AuthenticationState.unknown());
Future<void> init() async {
final _firstUser = await _authenticationRepository.user.first;
start();
return changeStatus(_firstUser);
}
bool start() {
_userSubscription = _authenticationRepository.user.listen(changeStatus);
return true;
}
bool stop() {
_userSubscription?.cancel();
return true;
}
Future<void> changeStatus(UserInterface user) async {
if (user.isNotEmpty) {
final Map<String, dynamic>? userData = await _onAuthSuccess?.call(user);
emit(AuthenticationState.authenticated(user, userData));
} else {
stop();
emit(const AuthenticationState.unauthenticated());
}
}
void logOut() {
unawaited(_authenticationRepository.signOut());
}
@override
Future<void> close() {
_userSubscription?.cancel();
return super.close();
}
}
@@ -14,5 +14,7 @@
// 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 'user_firebase.dart';
export 'user_interface.dart';
enum AuthCubitStatus {
started,
stoped,
}
@@ -14,7 +14,7 @@
// 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:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
class ApplyActionCodeFailureFirebase extends ApplyActionCodeFailureInterface {
ApplyActionCodeFailureFirebase([String? code, String? message])
@@ -148,32 +148,28 @@ class SignInWithCredentialFailureFirebase
class SignInWithGoogleFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithGoogleFailureInterface {
SignInWithGoogleFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code);
SignInWithGoogleFailureFirebase([super.code, super.message]);
SignInWithGoogleFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithFacebookFailureFirebase
extends SignInWithCredentialFailureFirebase
implements SignInWithFacebookFailureInterface {
SignInWithFacebookFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithFacebookFailureFirebase.fromCode(String code)
: super.fromCode(code);
SignInWithFacebookFailureFirebase([super.code, super.message]);
SignInWithFacebookFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class SignInWithAppleFailureFirebase extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithAppleFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithAppleFailureFirebase.fromCode(String code) : super.fromCode(code);
SignInWithAppleFailureFirebase([super.code, super.message]);
SignInWithAppleFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignInWithTwitterFailureFirebase extends SignInWithCredentialFailureFirebase
implements SignInWithAppleFailureInterface {
SignInWithTwitterFailureFirebase([String? code, String? message])
: super(code, message);
SignInWithTwitterFailureFirebase.fromCode(String code) : super.fromCode(code);
SignInWithTwitterFailureFirebase([super.code, super.message]);
SignInWithTwitterFailureFirebase.fromCode(super.code) : super.fromCode();
}
@@ -233,16 +229,16 @@ class SendEmailVerificationFailureFirebase
SendEmailVerificationFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendEmailVerificationFailureFirebase.fromCode(String code)
: super.fromCode(code);
SendEmailVerificationFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class SendPasswordResetEmailFailureFirebase
extends SendPasswordResetEmailFailureInterface {
SendPasswordResetEmailFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendPasswordResetEmailFailureFirebase.fromCode(String code)
: super.fromCode(code);
SendPasswordResetEmailFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class SendSignInLinkEmailFailureFirebase
@@ -250,8 +246,8 @@ class SendSignInLinkEmailFailureFirebase
SendSignInLinkEmailFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SendSignInLinkEmailFailureFirebase.fromCode(String code)
: super.fromCode(code);
SendSignInLinkEmailFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class ConfirmPasswordResetFailureFirebase
@@ -259,8 +255,8 @@ class ConfirmPasswordResetFailureFirebase
ConfirmPasswordResetFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
ConfirmPasswordResetFailureFirebase.fromCode(String code)
: super.fromCode(code);
ConfirmPasswordResetFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class VerifyPasswordResetCodeFailureFirebase
@@ -268,19 +264,19 @@ class VerifyPasswordResetCodeFailureFirebase
VerifyPasswordResetCodeFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
VerifyPasswordResetCodeFailureFirebase.fromCode(String code)
: super.fromCode(code);
VerifyPasswordResetCodeFailureFirebase.fromCode(super.code)
: super.fromCode();
}
class RefreshFailureFirebase extends RefreshFailureInterface {
RefreshFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
RefreshFailureFirebase.fromCode(String code) : super.fromCode(code);
RefreshFailureFirebase.fromCode(super.code) : super.fromCode();
}
class SignOutFailureFirebase extends SignOutFailureInterface {
SignOutFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
SignOutFailureFirebase.fromCode(String code) : super.fromCode(code);
SignOutFailureFirebase.fromCode(super.code) : super.fromCode();
}
@@ -14,5 +14,9 @@
// 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 'authentication_repository_firebase.dart';
export 'authentication_repository_interface.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:wyatt_authentication_bloc/src/data/models/user_firebase.dart';
extension FirebaseAuthUserX on User {
UserFirebase get model => UserFirebase(this);
}
@@ -1,24 +1,23 @@
// 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:firebase_auth/firebase_auth.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart' as wyatt;
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
class UserFirebase implements UserInterface {
class UserFirebase implements wyatt.User {
final User? _user;
const UserFirebase(User user) : _user = user;
@@ -14,19 +14,24 @@
// 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:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:twitter_login/twitter_login.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:wyatt_authentication_bloc/src/utils/cryptography.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/core/extensions/firebase_auth_user_x.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/cryptography.dart';
import 'package:wyatt_authentication_bloc/src/data/models/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart'
as wyatt;
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
class AuthenticationRepositoryFirebase
implements AuthenticationRepositoryInterface {
class AuthenticationRepositoryFirebase implements AuthenticationRepository {
final _controller = StreamController<AuthCubitStatus>();
final FirebaseAuth _firebaseAuth;
final TwitterLogin? _twitterLogin;
@@ -36,23 +41,30 @@ class AuthenticationRepositoryFirebase
FirebaseAuth? firebaseAuth,
TwitterLogin? twitterLogin,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_twitterLogin = twitterLogin;
@override
Stream<UserInterface> get user {
return _firebaseAuth.userChanges().map((User? firebaseUser) {
final UserFirebase user = firebaseUser == null
? const UserFirebase.empty()
: firebaseUser.model;
_userCache = user;
return user;
});
_twitterLogin = twitterLogin {
_controller.sink.add(AuthCubitStatus.stoped);
}
@override
UserInterface get currentUser {
return _userCache;
}
Stream<AuthCubitStatus> get cubitStatus =>
_controller.stream.asBroadcastStream();
@override
void changeCubitStatus(AuthCubitStatus status) =>
_controller.sink.add(status);
@override
Stream<wyatt.User> get user =>
_firebaseAuth.userChanges().map((firebaseUser) {
final UserFirebase user = (firebaseUser == null)
? const UserFirebase.empty()
: firebaseUser.model;
_userCache = user;
return user;
});
@override
wyatt.User get currentUser => _userCache;
@override
Future<void> applyActionCode(String code) async {
@@ -324,9 +336,3 @@ class AuthenticationRepositoryFirebase
}
}
}
extension on User {
UserFirebase get model {
return UserFirebase(this);
}
}
@@ -14,9 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class UserInterface {
abstract class User {
/// The empty user constructor.
const UserInterface.empty();
const User.empty();
/// The users display name.
///
@@ -14,22 +14,29 @@
// 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:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
/// {@template authentication_repository}
/// Repository which manages user authentication.
/// {@endtemplate}
abstract class AuthenticationRepositoryInterface {
/// Stream of [UserInterface] which will emit the current user when
abstract class AuthenticationRepository {
/// Stream of [AuthCubitStatus] wich will emit the current cubit status.
Stream<AuthCubitStatus> get cubitStatus;
/// Changes cubit status.(Useful to start or stop the engine.)
void changeCubitStatus(AuthCubitStatus status);
/// Stream of [User] which will emit the current user when
/// the authentication state changes.
///
/// Emits [UserInterface.empty] if the user is not authenticated.
Stream<UserInterface> get user;
/// Emits [User.empty] if the user is not authenticated.
Stream<User> get user;
/// Returns the current cached account.
/// Defaults to [UserInterface.empty] if there is no cached user.
UserInterface get currentUser;
/// Defaults to [User.empty] if there is no cached user.
User get currentUser;
/// Applies action code
///
@@ -122,7 +129,7 @@ abstract class AuthenticationRepositoryInterface {
Future<void> verifyPasswordResetCode({required String code});
/// Signs out the current user which will emit
/// [UserInterface.empty] from the [user] Stream.
/// [User.empty] from the [user] Stream.
Future<void> signOut();
/// Refreshes the current user.
@@ -16,41 +16,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
class AuthenticationBuilder extends StatelessWidget {
class AuthenticationBuilder<Extra> extends StatelessWidget {
const AuthenticationBuilder({
Key? key,
required this.authenticated,
required this.unauthenticated,
required this.unknown,
}) : super(key: key);
super.key,
});
final Widget Function(
BuildContext context,
UserInterface user,
Map<String, dynamic>? userData,
User user,
Extra? extra,
) authenticated;
final Widget Function(BuildContext context) unauthenticated;
final Widget Function(BuildContext context) unknown;
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthenticationCubit, AuthenticationState>(
builder: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
if (state.user != null) {
return authenticated(context, state.user!, state.userData);
} else {
Widget build(BuildContext context) =>
BlocBuilder<AuthenticationCubit<Extra>, AuthenticationState<Extra>>(
builder: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
if (state.user != null) {
return authenticated(context, state.user!, state.extra);
} else {
return unauthenticated(context);
}
} else if (state.status == AuthenticationStatus.unauthenticated) {
return unauthenticated(context);
} else {
return unknown(context);
}
} else if (state.status == AuthenticationStatus.unauthenticated) {
return unauthenticated(context);
} else {
return unknown(context);
}
},
);
}
},
);
}
@@ -0,0 +1,98 @@
// 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/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
part 'authentication_state.dart';
class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
final AuthenticationRepository _authenticationRepository;
late final StreamSubscription<AuthCubitStatus> _statusSubscription;
StreamSubscription<User>? _userSubscription;
final Future<Extra?> Function(User user)? _onAuthSuccess;
AuthenticationCubit({
required AuthenticationRepository authenticationRepository,
Future<Extra?> Function(User user)? onAuthSuccess,
}) : _authenticationRepository = authenticationRepository,
_onAuthSuccess = onAuthSuccess,
super(const AuthenticationState.unknown()) {
_subscribeStatus();
}
Future<AuthCubitStatus> get status async =>
_authenticationRepository.cubitStatus.last;
void _subscribeStatus() {
_statusSubscription = _authenticationRepository.cubitStatus.listen(
(status) {
switch (status) {
case AuthCubitStatus.started:
start();
break;
case AuthCubitStatus.stoped:
stop();
break;
}
},
);
}
Future<void> init() async {
final firstUser = await _authenticationRepository.user.first;
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
return changeStatus(firstUser);
}
bool start() {
_userSubscription = _authenticationRepository.user.listen(changeStatus);
return true;
}
bool stop() {
_userSubscription?.cancel();
return true;
}
Future<void> changeStatus(User user) async {
if (user.isNotEmpty) {
final Extra? extra = await _onAuthSuccess?.call(user);
emit(AuthenticationState.authenticated(user, extra));
} else {
_authenticationRepository.changeCubitStatus(AuthCubitStatus.stoped);
emit(const AuthenticationState.unauthenticated());
}
}
void logOut() {
unawaited(_authenticationRepository.signOut());
}
@override
Future<void> close() {
_userSubscription?.cancel();
_statusSubscription.cancel();
return super.close();
}
}
@@ -22,37 +22,36 @@ enum AuthenticationStatus {
unauthenticated,
}
class AuthenticationState extends Equatable {
class AuthenticationState<Extra> extends Equatable {
final AuthenticationStatus status;
final UserInterface? user;
final Map<String, dynamic>? userData;
final User? user;
final Extra? extra;
const AuthenticationState._({
required this.status,
this.user,
this.userData,
this.extra,
});
const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unknown);
const AuthenticationState.authenticated(
UserInterface user,
Map<String, dynamic>? userData,
User user,
Extra? extra,
) : this._(
status: AuthenticationStatus.authenticated,
user: user,
userData: userData,
extra: extra,
);
const AuthenticationState.unauthenticated()
: this._(status: AuthenticationStatus.unauthenticated);
@override
List<Object?> get props => [status, user, userData];
List<Object?> get props => [status, user, extra];
@override
// ignore: lines_longer_than_80_chars
String toString() =>
'AuthenticationState(status: $status, user: $user, userData: $userData)';
'AuthenticationState(status: $status, user: $user, extra: $extra)';
}
@@ -14,19 +14,21 @@
// 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:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart' show FormStatus;
part 'email_verification_state.dart';
class EmailVerificationCubit extends Cubit<EmailVerificationState> {
final AuthenticationRepositoryInterface _authenticationRepository;
final AuthenticationRepository _authenticationRepository;
EmailVerificationCubit(this._authenticationRepository)
: super(const EmailVerificationState());
EmailVerificationCubit({
required AuthenticationRepository authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
super(const EmailVerificationState());
Future<void> sendEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress));
@@ -31,13 +31,11 @@ class EmailVerificationState extends Equatable {
bool? isVerified,
FormStatus? status,
String? errorMessage,
}) {
return EmailVerificationState(
}) => EmailVerificationState(
isVerified: isVerified ?? this.isVerified,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object> get props => [isVerified, status];
@@ -14,32 +14,40 @@
// 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:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'password_reset_state.dart';
class PasswordResetCubit extends Cubit<PasswordResetState> {
final AuthenticationRepositoryInterface _authenticationRepository;
final AuthenticationRepository _authenticationRepository;
PasswordResetCubit(this._authenticationRepository)
: super(const PasswordResetState());
final FormValidator _validationStrategy;
PasswordResetCubit({
required AuthenticationRepository authenticationRepository,
FormValidator validationStrategy = const EveryInputValidator(),
}) : _authenticationRepository = authenticationRepository,
_validationStrategy = validationStrategy,
super(const PasswordResetState());
void emailChanged(String value) {
final Email email = Email.dirty(value);
emit(
state.copyWith(
email: email,
status: FormStatus.validate([email]),
status: _validationStrategy.rawValidate([email]),
),
);
}
Future<void> sendPasswordResetEmail() async {
if (!state.status.isValidated) return;
if (!state.status.isValidated) {
return;
}
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
await _authenticationRepository.sendPasswordResetEmail(
@@ -31,13 +31,11 @@ class PasswordResetState extends Equatable {
Email? email,
FormStatus? status,
String? errorMessage,
}) {
return PasswordResetState(
}) => PasswordResetState(
email: email ?? this.email,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object> get props => [email, status];
@@ -14,24 +14,25 @@
// 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:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'sign_in_state.dart';
class SignInCubit extends Cubit<SignInState> {
final AuthenticationRepositoryInterface _authenticationRepository;
final AuthenticationCubit _authenticationCubit;
final AuthenticationRepository _authenticationRepository;
final FormValidator _validationStrategy;
SignInCubit({
required AuthenticationRepositoryInterface authenticationRepository,
required AuthenticationCubit authenticationCubit,
required AuthenticationRepository authenticationRepository,
FormValidator validationStrategy = const EveryInputValidator(),
}) : _authenticationRepository = authenticationRepository,
_authenticationCubit = authenticationCubit,
_validationStrategy = validationStrategy,
super(const SignInState());
void emailChanged(String value) {
@@ -39,7 +40,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(
state.copyWith(
email: email,
status: FormStatus.validate([email, state.password]),
status: _validationStrategy.rawValidate([email, state.password]),
),
);
}
@@ -49,7 +50,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(
state.copyWith(
password: password,
status: FormStatus.validate([state.email, password]),
status: _validationStrategy.rawValidate([state.email, password]),
),
);
}
@@ -62,7 +63,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
await _authenticationRepository.signInAnonymously();
_authenticationCubit.start();
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
emit(state.copyWith(status: FormStatus.submissionSuccess));
} on SignInAnonymouslyFailureInterface catch (e) {
emit(
@@ -84,7 +85,7 @@ class SignInCubit extends Cubit<SignInState> {
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
await _authenticationRepository.signInWithGoogle();
_authenticationCubit.start();
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
emit(state.copyWith(status: FormStatus.submissionSuccess));
} on SignInWithGoogleFailureInterface catch (e) {
emit(
@@ -99,14 +100,16 @@ class SignInCubit extends Cubit<SignInState> {
}
Future<void> signInWithEmailAndPassword() async {
if (!state.status.isValidated) return;
if (!state.status.isValidated) {
return;
}
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
await _authenticationRepository.signInWithEmailAndPassword(
email: state.email.value,
password: state.password.value,
);
_authenticationCubit.start();
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
emit(state.copyWith(status: FormStatus.submissionSuccess));
} on SignInWithEmailAndPasswordFailureInterface catch (e) {
emit(
@@ -34,25 +34,22 @@ class SignInState extends Equatable {
Password? password,
FormStatus? status,
String? errorMessage,
}) {
return SignInState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}) =>
SignInState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
List<Object> get props => [email, password, status];
@override
String toString() {
return '''
String toString() => '''
email: $email,
password: $password,
status: $status,
errorMessage: $errorMessage,
''';
}
}
@@ -16,30 +16,31 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'sign_up_state.dart';
class SignUpCubit extends Cubit<SignUpState> {
final AuthenticationRepositoryInterface _authenticationRepository;
final AuthenticationCubit _authenticationCubit;
final AuthenticationRepository _authenticationRepository;
final Future Function(SignUpState state, String? uid)? _onSignUpSuccess;
final Future<void> Function(SignUpState state, String? uid)? _onSignUpSuccess;
final FormValidator _validationStrategy;
SignUpCubit({
required AuthenticationRepositoryInterface authenticationRepository,
required AuthenticationCubit authenticationCubit,
required FormData entries,
Future Function(SignUpState state, String? uid)? onSignUpSuccess,
required AuthenticationRepository authenticationRepository,
required FormData formData,
FormValidator validationStrategy = const EveryInputValidator(),
Future<void> Function(SignUpState state, String? uid)? onSignUpSuccess,
}) : _authenticationRepository = authenticationRepository,
_authenticationCubit = authenticationCubit,
_onSignUpSuccess = onSignUpSuccess,
super(SignUpState(data: entries));
_validationStrategy = validationStrategy,
super(SignUpState(data: formData));
void emailChanged(String value) {
final Email email = Email.dirty(value);
@@ -53,7 +54,7 @@ class SignUpCubit extends Cubit<SignUpState> {
emit(
state.copyWith(
email: email,
status: FormStatus.validate(toValidate),
status: _validationStrategy.rawValidate(toValidate),
),
);
}
@@ -70,25 +71,28 @@ class SignUpCubit extends Cubit<SignUpState> {
emit(
state.copyWith(
password: password,
status: FormStatus.validate(toValidate),
status: _validationStrategy.rawValidate(toValidate),
),
);
}
// Take from wyatt_form_bloc/wyatt_form_bloc.dart
void dataChanged<T>(String field, FormInputValidator dirtyValue) {
final _form = state.data.clone();
void dataChanged<T>(
String field,
FormInputValidator<T, ValidationError> dirtyValue,
) {
final form = state.data.clone();
if (_form.contains(field)) {
_form.updateValidator(field, dirtyValue);
if (form.contains(field)) {
form.updateValidator(field, dirtyValue);
} else {
throw Exception('Form field $field not found');
}
emit(
state.copyWith(
data: _form,
status: FormStatus.validate(
data: form,
status: _validationStrategy.rawValidate(
[
state.email,
state.password,
@@ -104,27 +108,27 @@ class SignUpCubit extends Cubit<SignUpState> {
FormData data, {
SetOperation operation = SetOperation.replace,
}) {
FormData _form = data;
FormData form = data;
switch (operation) {
case SetOperation.replace:
_form = data;
form = data;
break;
case SetOperation.difference:
_form = state.data.difference(data);
form = state.data.difference(data);
break;
case SetOperation.intersection:
_form = state.data.intersection(data);
form = state.data.intersection(data);
break;
case SetOperation.union:
_form = state.data.union(data);
form = state.data.union(data);
break;
}
emit(
state.copyWith(
data: _form,
status: FormStatus.validate(
data: form,
status: _validationStrategy.rawValidate(
[
state.email,
state.password,
@@ -136,7 +140,9 @@ class SignUpCubit extends Cubit<SignUpState> {
}
Future<void> signUpFormSubmitted() async {
if (!state.status.isValidated) return;
if (!state.status.isValidated) {
return;
}
emit(state.copyWith(status: FormStatus.submissionInProgress));
try {
final uid = await _authenticationRepository.signUp(
@@ -144,16 +150,8 @@ class SignUpCubit extends Cubit<SignUpState> {
password: state.password.value,
);
await _onSignUpSuccess?.call(state, uid);
if (_authenticationCubit.start()) {
emit(state.copyWith(status: FormStatus.submissionSuccess));
} else {
emit(
state.copyWith(
errorMessage: 'Failed to start authentication cubit',
status: FormStatus.submissionFailure,
),
);
}
_authenticationRepository.changeCubitStatus(AuthCubitStatus.started);
emit(state.copyWith(status: FormStatus.submissionSuccess));
} on SignUpWithEmailAndPasswordFailureInterface catch (e) {
emit(
state.copyWith(
@@ -16,8 +16,7 @@
part of 'sign_up_cubit.dart';
@immutable
class SignUpState {
class SignUpState extends Equatable {
final Email email;
final Password password;
final FormStatus status;
@@ -25,10 +24,10 @@ class SignUpState {
final String? errorMessage;
const SignUpState({
required this.data,
this.email = const Email.pure(),
this.password = const Password.pure(),
this.status = FormStatus.pure,
required this.data,
this.errorMessage,
});
@@ -38,40 +37,19 @@ class SignUpState {
FormStatus? status,
FormData? data,
String? errorMessage,
}) {
return SignUpState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
data: data ?? this.data,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}) =>
SignUpState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
data: data ?? this.data,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is SignUpState &&
other.email == email &&
other.password == password &&
other.status == status &&
other.data == data &&
other.errorMessage == errorMessage;
}
String toString() => 'SignUpState(email: $email, password: $password, '
'status: $status, data: $data, errorMessage: $errorMessage)';
@override
int get hashCode {
return email.hashCode ^
password.hashCode ^
status.hashCode ^
data.hashCode ^
errorMessage.hashCode;
}
@override
String toString() {
// ignore: lines_longer_than_80_chars
return 'SignUpState(email: $email, password: $password, status: $status, data: $data, errorMessage: $errorMessage)';
}
List<Object?> get props => [email, password, status, data, errorMessage];
}
@@ -1,18 +0,0 @@
// 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 'exceptions_firebase.dart';
export 'exceptions_interface.dart';
@@ -1,18 +0,0 @@
// 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 'exceptions/exceptions.dart';
export 'user/user.dart';
@@ -14,10 +14,17 @@
// 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 'authentication/authentication.dart';
export 'email_verification/email_verification.dart';
export 'models/models.dart';
export 'password_reset/password_reset.dart';
export 'repositories/repositories.dart';
export 'sign_in/sign_in.dart';
export 'sign_up/sign_up.dart';
export 'core/enum/auth_cubit_status.dart';
export 'core/exceptions/exceptions.dart';
export 'core/exceptions/exceptions_firebase.dart';
export 'core/extensions/firebase_auth_user_x.dart';
export 'core/utils/cryptography.dart';
export 'data/models/user_firebase.dart';
export 'data/repositories/authentication_repository_firebase.dart';
export 'domain/entities/user.dart';
export 'domain/repositories/authentication_repository.dart';
export 'features/authentication/authentication.dart';
export 'features/email_verification/email_verification.dart';
export 'features/password_reset/password_reset.dart';
export 'features/sign_in/sign_in.dart';
export 'features/sign_up/sign_up.dart';