master #81

Closed
malo wants to merge 322 commits from master into feat/bloc_layout/new-package
32 changed files with 319 additions and 262 deletions
Showing only changes of commit b83275aaf6 - Show all commits

View File

@ -37,7 +37,7 @@ class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
} }
@override @override
FutureOrResult<int?> onSignInFromCache(SessionWrapper<int> wrapper) { FutureOrResult<int?> onSignInFromCache(AuthenticationSession<int> session) {
print('onSignInFromCache'); print('onSignInFromCache');
return const Ok(1); return const Ok(1);

View File

@ -30,8 +30,7 @@ class HomePage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text('Home | ${context.watchAccount<int>()?.email}'),
'Home | ${context.account<AuthenticationCubit<int>, int>()?.email}'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => onPressed: () =>
@ -44,8 +43,8 @@ class HomePage extends StatelessWidget {
child: ListView( child: ListView(
children: [ children: [
AuthenticationBuilder<int>( AuthenticationBuilder<int>(
authenticated: (context, sessionWrapper) => Text( authenticated: (context, session) => Text(
'Logged as ${sessionWrapper.session?.account.email} | GeneratedId is ${sessionWrapper.session?.data}'), 'Logged as ${session.account?.email} | GeneratedId is ${session.data}'),
unauthenticated: (context) => unauthenticated: (context) =>
const Text('Not logged (unauthenticated)'), const Text('Not logged (unauthenticated)'),
unknown: (context) => const Text('Not logged (unknown)'), unknown: (context) => const Text('Not logged (unknown)'),

View File

@ -17,26 +17,82 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart'; import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
/// Extension that helps to quickly access useful resources like wrapper, /// Extension that helps to quickly access useful resources like wrapper,
/// session, account or data. /// session, account or data.
extension BuildContextExtension on BuildContext { extension BuildContextExtension on BuildContext {
/// Returns session wrapper /// Read session in context from a specific AuthenticationCubit type [T]
SessionWrapper<Data>? wrapper<T extends AuthenticationCubit<Data>, Data>() => AuthenticationSession<Data>?
readSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
read<T>().currentSession();
/// Watch session in context from a specific AuthenticationCubit type [T]
AuthenticationSession<Data>?
watchSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession(); watch<T>().currentSession();
/// Returns session /// Read session in context from generic AuthenticationCubit type
Session<Data>? session<T extends AuthenticationCubit<Data>, Data>() => AuthenticationSession<Data>? readSession<Data>() =>
watch<T>().currentSession()?.session; read<AuthenticationCubit<Data>>().currentSession();
/// Returns account /// Watch session in context from generic AuthenticationCubit type
Account? account<T extends AuthenticationCubit<Data>, Data>() => AuthenticationSession<Data>? watchSession<Data>() =>
watch<T>().currentSession()?.session?.account; watch<AuthenticationCubit<Data>>().currentSession();
/// Returns associated data /// Read event in context from a specific AuthenticationCubit type [T]
Data? data<T extends AuthenticationCubit<Data>, Data>() => AuthenticationChangeEvent?
watch<T>().currentSession()?.session?.data; readEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.latestEvent;
/// Watch event in context from a specific AuthenticationCubit type [T]
AuthenticationChangeEvent?
watchEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.latestEvent;
/// Read event in context from generic AuthenticationCubit type
AuthenticationChangeEvent? readEvent<Data>() =>
readSession<Data>()?.latestEvent;
/// Watch event in context from generic AuthenticationCubit type
AuthenticationChangeEvent? watchEvent<Data>() =>
watchSession<Data>()?.latestEvent;
/// Read account in context from a specific AuthenticationCubit type [T]
Account? readAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.account;
/// Watch account in context from a specific AuthenticationCubit type [T]
Account? watchAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.account;
/// Read account in context from generic AuthenticationCubit type
Account? readAccount<Data>() => readSession<Data>()?.account;
/// Watch account in context from generic AuthenticationCubit type
Account? watchAccount<Data>() => watchSession<Data>()?.account;
/// Read data in context from a specific AuthenticationCubit type [T]
Data? readDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.data;
/// Watch data in context from a specific AuthenticationCubit type [T]
Data? watchDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.data;
/// Read data in context from generic AuthenticationCubit type
Data? readData<Data>() => readSession<Data>()?.data;
/// Watch data in context from generic AuthenticationCubit type
Data? watchData<Data>() => watchSession<Data>()?.data;
/// Check if user is authenticated from a
/// specific AuthenticationCubit type [T]
bool isAuthenticatedFrom<T extends AuthenticationCubit<Data>, Data>() =>
readEventFrom<T, Data>() is AuthenticatedChangeEvent;
/// Check if user is authenticated from generic AuthenticationCubit type
bool isAuthenticated<Data>() => readEvent<Data>() is AuthenticatedChangeEvent;
} }

View File

@ -23,8 +23,8 @@ import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/data/models/models.dart'; import 'package:wyatt_authentication_bloc/src/data/models/models.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart'; import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationFirebaseDataSourceImpl<Data> class AuthenticationFirebaseDataSourceImpl<Data>
@ -41,7 +41,7 @@ class AuthenticationFirebaseDataSourceImpl<Data>
_checkForCachedAccount(); _checkForCachedAccount();
} }
late StreamController<SessionWrapper<Data>> _sessionStream; late StreamController<AuthenticationSession<Data>> _sessionStream;
late StreamController<UserCredential?> _latestCredentials; late StreamController<UserCredential?> _latestCredentials;
final FirebaseAuth _firebaseAuth; final FirebaseAuth _firebaseAuth;
@ -51,8 +51,11 @@ class AuthenticationFirebaseDataSourceImpl<Data>
final currentUser = _firebaseAuth.currentUser; final currentUser = _firebaseAuth.currentUser;
if (currentUser == null) { if (currentUser == null) {
_sessionStream _sessionStream.add(
.add(const SessionWrapper(event: UnknownAuthenticationEvent())); const AuthenticationSession(
latestEvent: UnknownAuthenticationEvent(),
),
);
return; return;
} }
@ -62,7 +65,9 @@ class AuthenticationFirebaseDataSourceImpl<Data>
accessToken: jwt, accessToken: jwt,
); );
_sessionStream.add( _sessionStream.add(
SessionWrapper(event: SignedInFromCacheEvent(account: currentAccount)), AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: currentAccount),
),
); );
return; return;
} }
@ -78,19 +83,23 @@ class AuthenticationFirebaseDataSourceImpl<Data>
return account; return account;
} }
// Stream related methods =================================================== // Session related methods ===================================================
/// {@macro add_session} /// {@macro add_session}
@override @override
void addSession(SessionWrapper<Data> wrapper) { void addSession(AuthenticationSession<Data> session) {
_sessionStream.add(wrapper); _sessionStream.add(session);
} }
/// {@macro session_stream} /// {@macro session_stream}
@override @override
Stream<SessionWrapper<Data>> sessionStream() => Stream<AuthenticationSession<Data>> sessionStream() =>
_sessionStream.stream.asBroadcastStream(); _sessionStream.stream.asBroadcastStream();
/// {@macro current_session}
@override
Future<AuthenticationSession<Data>> currentSession() => sessionStream().last;
// SignUp/SignIn methods ==================================================== // SignUp/SignIn methods ====================================================
/// {@macro signup_pwd} /// {@macro signup_pwd}

View File

@ -18,7 +18,7 @@ import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart'; import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart'; import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart'; import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@ -73,18 +73,30 @@ class AuthenticationRepositoryImpl<Data extends Object>
@override @override
FormRepository get formRepository => _formRepository; FormRepository get formRepository => _formRepository;
// Stream related methods =================================================== // Session related methods ===================================================
/// {@macro add_session} /// {@macro add_session}
@override @override
void addSession(SessionWrapper<Data> wrapper) => void addSession(AuthenticationSession<Data> session) =>
authenticationRemoteDataSource.addSession(wrapper); authenticationRemoteDataSource.addSession(session);
/// {@macro session_stream} /// {@macro session_stream}
@override @override
Stream<SessionWrapper<Data>> sessionStream() => Stream<AuthenticationSession<Data>> sessionStream() =>
authenticationRemoteDataSource.sessionStream(); authenticationRemoteDataSource.sessionStream();
/// {@macro current_session}
@override
FutureOrResult<AuthenticationSession<Data>> currentSession() =>
Result.tryCatchAsync<AuthenticationSession<Data>, AppException,
AppException>(
() async {
final session = await authenticationRemoteDataSource.currentSession();
return session;
},
(error) => error,
);
// SignUp/SignIn methods ==================================================== // SignUp/SignIn methods ====================================================
/// {@macro signup_pwd} /// {@macro signup_pwd}

View File

@ -16,16 +16,16 @@
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';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
/// Is responsible for abstracting the provenance of the data. /// Is responsible for abstracting the provenance of the data.
abstract class AuthenticationRemoteDataSource<Data> abstract class AuthenticationRemoteDataSource<Data>
extends BaseRemoteDataSource { extends BaseRemoteDataSource {
// Stream related methods =================================================== // Session related methods ===================================================
void addSession(SessionWrapper<Data> wrapper); void addSession(AuthenticationSession<Data> session);
Stream<AuthenticationSession<Data>> sessionStream();
Stream<SessionWrapper<Data>> sessionStream(); Future<AuthenticationSession<Data>> currentSession();
// SignUp/SignIn methods ==================================================== // SignUp/SignIn methods ====================================================

View File

@ -0,0 +1,60 @@
// Copyright (C) 2023 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:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// The [AuthenticationSession] object is used to transport and propagate
/// the last event issued by an authentication state change, a user account
/// if connected, and the associated data.
class AuthenticationSession<Data> extends Equatable {
const AuthenticationSession({
required this.latestEvent,
this.account,
this.data,
});
factory AuthenticationSession.fromEvent(
AuthenticationChangeEvent latestEvent, {
Data? data,
}) {
if (latestEvent is AuthenticatedChangeEvent) {
return AuthenticationSession(
latestEvent: latestEvent,
account: latestEvent.account,
data: data,
);
}
return AuthenticationSession(
latestEvent: latestEvent,
data: data,
);
}
final AuthenticationChangeEvent latestEvent;
final Account? account;
final Data? data;
@override
List<Object?> get props => [
latestEvent,
account,
data,
];
@override
bool? get stringify => true;
}

View File

@ -14,23 +14,15 @@
// 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'; part of 'authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
/// The [Session] object is used to transport and propagate
/// the connected user [Account] and personalized [Data] in the application. /// Represents every event where user is authenticated.
class Session<Data> extends Equatable { abstract class AuthenticatedChangeEvent extends AuthenticationChangeEvent {
const Session({ const AuthenticatedChangeEvent({required this.account});
required this.account,
this.data,
});
final Account account; final Account account;
final Data? data;
@override @override
List<Object?> get props => [account, data]; List<Object?> get props => [account];
@override
bool? get stringify => true;
} }

View File

@ -18,15 +18,16 @@ 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';
part 'authenticated_change_event.dart';
part 'deleted_event.dart';
part 'reauthenticated_event.dart';
part 'refreshed_event.dart';
part 'signed_in_event.dart'; part 'signed_in_event.dart';
part 'signed_in_from_cache_event.dart';
part 'signed_out_event.dart'; part 'signed_out_event.dart';
part 'signed_up_event.dart'; part 'signed_up_event.dart';
part 'refreshed_event.dart';
part 'reauthenticated_event.dart';
part 'updated_event.dart';
part 'unknown_authentication_event.dart'; part 'unknown_authentication_event.dart';
part 'signed_in_from_cache_event.dart'; part 'updated_event.dart';
part 'deleted_event.dart';
/// Represents an event initiated by a change in /// Represents an event initiated by a change in
/// the user's authentication status. /// the user's authentication status.

View File

@ -19,11 +19,6 @@ part of 'authentication_change_event.dart';
/// When a user re-authenticates (from the logged in state to the /// When a user re-authenticates (from the logged in state to the
/// logged in state with a different and fresh access /// logged in state with a different and fresh access
/// token and a different login time) /// token and a different login time)
class ReauthenticatedEvent extends AuthenticationChangeEvent { class ReauthenticatedEvent extends AuthenticatedChangeEvent {
const ReauthenticatedEvent({required this.account}); const ReauthenticatedEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -18,11 +18,6 @@ part of 'authentication_change_event.dart';
/// When a user access token is refreshed (from the logged in state to the /// When a user access token is refreshed (from the logged in state to the
/// logged in state with a different access token) /// logged in state with a different access token)
class RefreshedEvent extends AuthenticationChangeEvent { class RefreshedEvent extends AuthenticatedChangeEvent {
const RefreshedEvent({required this.account}); const RefreshedEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart'; part of 'authentication_change_event.dart';
/// When a user authenticates (from not logged in to logged in). /// When a user authenticates (from not logged in to logged in).
class SignedInEvent extends AuthenticationChangeEvent { class SignedInEvent extends AuthenticatedChangeEvent {
const SignedInEvent({required this.account}); const SignedInEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart'; part of 'authentication_change_event.dart';
/// When a user authenticates automatically (from not logged in to logged in). /// When a user authenticates automatically (from not logged in to logged in).
class SignedInFromCacheEvent extends AuthenticationChangeEvent { class SignedInFromCacheEvent extends AuthenticatedChangeEvent {
const SignedInFromCacheEvent({required this.account}); const SignedInFromCacheEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart'; part of 'authentication_change_event.dart';
/// When a user creates an account. /// When a user creates an account.
class SignedUpEvent extends AuthenticationChangeEvent { class SignedUpEvent extends AuthenticatedChangeEvent {
const SignedUpEvent({required this.account}); const SignedUpEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart'; part of 'authentication_change_event.dart';
/// When the user's account has been updated. /// When the user's account has been updated.
class UpdatedEvent extends AuthenticationChangeEvent { class UpdatedEvent extends AuthenticatedChangeEvent {
const UpdatedEvent({required this.account}); const UpdatedEvent({required super.account});
final Account account;
@override
List<Object?> get props => [account];
} }

View File

@ -15,6 +15,5 @@
// 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 'account.dart'; export 'account.dart';
export 'auth_session.dart';
export 'authentication_change_event/authentication_change_event.dart'; export 'authentication_change_event/authentication_change_event.dart';
export 'session.dart';
export 'session_wrapper.dart';

View File

@ -1,38 +0,0 @@
// Copyright (C) 2023 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:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session.dart';
/// Contains the [AuthenticationChangeEvent] initiating the state
/// change and the current [Session].
class SessionWrapper<Data> extends Equatable implements Entity {
const SessionWrapper({
required this.event,
this.session,
});
final AuthenticationChangeEvent event;
final Session<Data>? session;
@override
List<Object?> get props => [event, session];
@override
bool get stringify => true;
}

View File

@ -16,7 +16,7 @@
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';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
abstract class AuthenticationRepository<Data> extends BaseRepository { abstract class AuthenticationRepository<Data> extends BaseRepository {
@ -30,12 +30,17 @@ abstract class AuthenticationRepository<Data> extends BaseRepository {
/// {@template add_session} /// {@template add_session}
/// Add a new authentication event. /// Add a new authentication event.
/// {@endtemplate} /// {@endtemplate}
void addSession(SessionWrapper<Data> wrapper); void addSession(AuthenticationSession<Data> session);
/// {@template session_stream} /// {@template session_stream}
/// Authentication state change event stream. /// Authentication state change event stream.
/// {@endtemplate} /// {@endtemplate}
Stream<SessionWrapper<Data>> sessionStream(); Stream<AuthenticationSession<Data>> sessionStream();
/// {@template current_session}
/// Latest session issued by the session stream.
/// {@endtemplate}
FutureOrResult<AuthenticationSession<Data>> currentSession();
// SignUp/SignIn methods ==================================================== // SignUp/SignIn methods ====================================================

View File

@ -17,7 +17,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.dart'; import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart'; import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
class AuthenticationBuilder<Data> extends StatelessWidget { class AuthenticationBuilder<Data> extends StatelessWidget {
@ -30,7 +30,7 @@ class AuthenticationBuilder<Data> extends StatelessWidget {
final Widget Function( final Widget Function(
BuildContext context, BuildContext context,
SessionWrapper<Data> sessionWrapper, AuthenticationSession<Data> session,
) authenticated; ) authenticated;
final Widget Function(BuildContext context) unauthenticated; final Widget Function(BuildContext context) unauthenticated;
final Widget Function(BuildContext context) unknown; final Widget Function(BuildContext context) unknown;
@ -40,8 +40,8 @@ class AuthenticationBuilder<Data> extends StatelessWidget {
BlocBuilder<AuthenticationCubit<Data>, AuthenticationState<Data>>( BlocBuilder<AuthenticationCubit<Data>, AuthenticationState<Data>>(
builder: (context, state) { builder: (context, state) {
if (state.status == AuthenticationStatus.authenticated) { if (state.status == AuthenticationStatus.authenticated) {
if (state.wrapper != null) { if (state.session != null) {
return authenticated(context, state.wrapper!); return authenticated(context, state.session!);
} else { } else {
return unauthenticated(context); return unauthenticated(context);
} }

View File

@ -42,35 +42,31 @@ abstract class AuthenticationCubit<Data>
} }
final AuthenticationRepository<Data> authenticationRepository; final AuthenticationRepository<Data> authenticationRepository;
SessionWrapper<Data>? _latestSession; AuthenticationSession<Data>? _latestSession;
void _listenForAuthenticationChanges() { void _listenForAuthenticationChanges() {
authenticationRepository.sessionStream().asyncMap((wrapper) async { authenticationRepository.sessionStream().asyncMap((session) async {
final event = wrapper.event; final event = session.latestEvent;
if (event is SignedInFromCacheEvent) { if (event is SignedInFromCacheEvent) {
final customRoutineResult = await onSignInFromCache(wrapper); final customRoutineResult = await onSignInFromCache(session);
if (customRoutineResult.isOk) { if (customRoutineResult.isOk) {
final account = event.account; final account = event.account;
final sessionData = customRoutineResult.ok; final sessionData = customRoutineResult.ok;
final signedInSession = SessionWrapper( final signedInSession = AuthenticationSession.fromEvent(
event: SignedInFromCacheEvent(account: account), SignedInFromCacheEvent(account: account),
session: Session<Data>(
account: account,
data: sessionData, data: sessionData,
),
); );
return signedInSession; return signedInSession;
} }
} }
return wrapper; return session;
}).listen((wrapper) async { }).listen((session) async {
_latestSession = wrapper; _latestSession = session;
final session = wrapper.session; if (session.account != null) {
if (session != null) { emit(AuthenticationState<Data>.authenticated(session));
emit(AuthenticationState<Data>.authenticated(wrapper));
return; return;
} }
emit(AuthenticationState<Data>.unauthenticated()); emit(AuthenticationState<Data>.unauthenticated());
@ -84,14 +80,11 @@ abstract class AuthenticationCubit<Data>
attachedLogic: onRefresh, attachedLogic: onRefresh,
onError: addError, onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession( onSuccess: (result, data) => authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: RefreshedEvent(account: result), RefreshedEvent(account: result),
session: Session<Data>(
account: result,
data: data, data: data,
), ),
), ),
),
).call(); ).call();
/// {@macro reauthenticate} /// {@macro reauthenticate}
@ -100,14 +93,11 @@ abstract class AuthenticationCubit<Data>
attachedLogic: onReauthenticate, attachedLogic: onReauthenticate,
onError: addError, onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession( onSuccess: (result, data) => authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: ReauthenticatedEvent(account: result), ReauthenticatedEvent(account: result),
session: Session<Data>(
account: result,
data: data, data: data,
), ),
), ),
),
).call(); ).call();
/// {@macro signout} /// {@macro signout}
@ -115,8 +105,11 @@ abstract class AuthenticationCubit<Data>
routine: authenticationRepository.signOut, routine: authenticationRepository.signOut,
attachedLogic: (routineResult) => onSignOut(), attachedLogic: (routineResult) => onSignOut(),
onError: addError, onError: addError,
onSuccess: (result, data) => authenticationRepository onSuccess: (result, data) => authenticationRepository.addSession(
.addSession(SessionWrapper<Data>(event: const SignedOutEvent())), const AuthenticationSession(
latestEvent: SignedOutEvent(),
),
),
).call(); ).call();
/// {@macro delete} /// {@macro delete}
@ -124,20 +117,23 @@ abstract class AuthenticationCubit<Data>
routine: authenticationRepository.delete, routine: authenticationRepository.delete,
attachedLogic: (routineResult) => onDelete(), attachedLogic: (routineResult) => onDelete(),
onError: addError, onError: addError,
onSuccess: (result, data) => authenticationRepository onSuccess: (result, data) => authenticationRepository.addSession(
.addSession(SessionWrapper<Data>(event: const DeletedEvent())), const AuthenticationSession(
latestEvent: DeletedEvent(),
),
),
).call(); ).call();
/// Returns latest session wrapper. /// Returns latest session.
/// ///
/// Contains latest event and latest session data (account + extra data) /// Contains latest event and latest session data (account + extra data)
SessionWrapper<Data>? currentSession() => _latestSession; AuthenticationSession<Data>? currentSession() => _latestSession;
/// This callback is triggered when the user is automaticcaly logged in from /// This callback is triggered when the user is automaticcaly logged in from
/// the cache. /// the cache.
/// ///
/// For example: when the user is sign in from the Firebase cache. /// For example: when the user is sign in from the Firebase cache.
FutureOrResult<Data?> onSignInFromCache(SessionWrapper<Data> wrapper); FutureOrResult<Data?> onSignInFromCache(AuthenticationSession<Data> session);
/// This callback is triggered when the account is refreshed. /// This callback is triggered when the account is refreshed.
/// ///

View File

@ -17,25 +17,25 @@
part of 'authentication_cubit.dart'; part of 'authentication_cubit.dart';
class AuthenticationState<Data> extends Equatable { class AuthenticationState<Data> extends Equatable {
const AuthenticationState._(this.status, this.wrapper); const AuthenticationState._(this.status, this.session);
const AuthenticationState.unauthenticated() const AuthenticationState.unauthenticated()
: this._(AuthenticationStatus.unauthenticated, null); : this._(AuthenticationStatus.unauthenticated, null);
const AuthenticationState.authenticated(SessionWrapper<Data> sessionWrapper) const AuthenticationState.authenticated(AuthenticationSession<Data> session)
: this._( : this._(
AuthenticationStatus.authenticated, AuthenticationStatus.authenticated,
sessionWrapper, session,
); );
const AuthenticationState.unknown() const AuthenticationState.unknown()
: this._(AuthenticationStatus.unknown, null); : this._(AuthenticationStatus.unknown, null);
final AuthenticationStatus status; final AuthenticationStatus status;
final SessionWrapper<Data>? wrapper; final AuthenticationSession<Data>? session;
@override @override
List<Object?> get props => [status, wrapper]; List<Object?> get props => [status, session];
@override @override
bool? get stringify => true; bool? get stringify => true;

View File

@ -111,13 +111,10 @@ mixin UpdateEmail<Data> on BaseEditAccountCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: UpdatedEvent(account: account), UpdatedEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
EditAccountState( EditAccountState(

View File

@ -110,13 +110,10 @@ mixin UpdatePassword<Data> on BaseEditAccountCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), UpdatedEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
EditAccountState( EditAccountState(

View File

@ -31,12 +31,13 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
final AuthenticationRepository<Data> authenticationRepository; final AuthenticationRepository<Data> authenticationRepository;
FutureOr<void> sendEmailVerification() async { FutureOr<void> sendEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress)); emit(const EmailVerificationState(status: FormStatus.submissionInProgress));
final response = await authenticationRepository.sendEmailVerification(); final response = await authenticationRepository.sendEmailVerification();
emit( emit(
response.fold( response.fold(
(value) => state.copyWith(status: FormStatus.submissionSuccess), (value) =>
(error) => state.copyWith( const EmailVerificationState(status: FormStatus.submissionSuccess),
(error) => EmailVerificationState(
errorMessage: error.message, errorMessage: error.message,
status: FormStatus.submissionFailure, status: FormStatus.submissionFailure,
), ),
@ -45,7 +46,7 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
} }
FutureOr<void> checkEmailVerification() async { FutureOr<void> checkEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress)); emit(const EmailVerificationState(status: FormStatus.submissionInProgress));
final refresh = await authenticationRepository.refresh(); final refresh = await authenticationRepository.refresh();
if (refresh.isErr) { if (refresh.isErr) {
@ -59,11 +60,11 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
return; return;
} }
final wrapper = await authenticationRepository.sessionStream().last; final session = await authenticationRepository.currentSession();
final currentAccount = wrapper.session?.account; final currentAccount = session.ok?.account;
if (currentAccount != null) { if (currentAccount != null) {
emit( emit(
state.copyWith( EmailVerificationState(
isVerified: currentAccount.emailVerified, isVerified: currentAccount.emailVerified,
status: FormStatus.submissionSuccess, status: FormStatus.submissionSuccess,
), ),

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP // Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details. // Please see the AUTHORS file for details.
// //
@ -18,16 +17,16 @@
part of 'email_verification_cubit.dart'; part of 'email_verification_cubit.dart';
class EmailVerificationState extends Equatable { class EmailVerificationState extends Equatable {
final FormStatus status;
final bool isVerified;
final String? errorMessage;
const EmailVerificationState({ const EmailVerificationState({
this.isVerified = false, this.isVerified = false,
this.status = FormStatus.pure, this.status = FormStatus.pure,
this.errorMessage, this.errorMessage,
}); });
final FormStatus status;
final bool isVerified;
final String? errorMessage;
EmailVerificationState copyWith({ EmailVerificationState copyWith({
FormStatus? status, FormStatus? status,
bool? isVerified, bool? isVerified,

View File

@ -63,13 +63,10 @@ mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
SignInState( SignInState(

View File

@ -136,13 +136,10 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
SignInState( SignInState(

View File

@ -62,13 +62,10 @@ mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
SignInState( SignInState(

View File

@ -126,13 +126,10 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
}, },
onSuccess: (account, data) { onSuccess: (account, data) {
authenticationRepository.addSession( authenticationRepository.addSession(
SessionWrapper( AuthenticationSession.fromEvent(
event: SignedUpEvent(account: account), SignedUpEvent(account: account),
session: Session<Data>(
account: account,
data: data, data: data,
), ),
),
); );
emit( emit(
SignUpState( SignUpState(

View File

@ -43,7 +43,9 @@ class TestAuthenticationCubit extends AuthenticationCubit<int> {
const Ok(null); const Ok(null);
@override @override
FutureOrResult<int?> onSignInFromCache(SessionWrapper<int> wrapper) async => FutureOrResult<int?> onSignInFromCache(
AuthenticationSession<int> session,
) async =>
const Ok(null); const Ok(null);
@override @override
@ -53,9 +55,10 @@ class TestAuthenticationCubit extends AuthenticationCubit<int> {
void main() { void main() {
group('AuthenticationCubit<T>', () { group('AuthenticationCubit<T>', () {
final MockAccount account = MockAccount(); final MockAccount account = MockAccount();
final SessionWrapper<int> wrapper = SessionWrapper( final AuthenticationSession<int> session = AuthenticationSession<int>(
event: const UnknownAuthenticationEvent(), latestEvent: const UnknownAuthenticationEvent(),
session: Session(account: account, data: 10), account: account,
data: 10,
); );
late AuthenticationRepository<int> authenticationRepository; late AuthenticationRepository<int> authenticationRepository;
@ -80,14 +83,14 @@ void main() {
'emits authenticated when stream contains session', 'emits authenticated when stream contains session',
setUp: () { setUp: () {
when(() => authenticationRepository.sessionStream()).thenAnswer( when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable([wrapper]), (_) => Stream.fromIterable([session]),
); );
}, },
build: () => TestAuthenticationCubit( build: () => TestAuthenticationCubit(
authenticationRepository: authenticationRepository, authenticationRepository: authenticationRepository,
), ),
seed: () => const AuthenticationState.unknown(), seed: () => const AuthenticationState.unknown(),
expect: () => [AuthenticationState<int>.authenticated(wrapper)], expect: () => [AuthenticationState<int>.authenticated(session)],
); );
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>( blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
@ -95,7 +98,8 @@ void main() {
setUp: () { setUp: () {
when(() => authenticationRepository.sessionStream()).thenAnswer( when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable( (_) => Stream.fromIterable(
[const SessionWrapper(event: SignedOutEvent())],), [const AuthenticationSession(latestEvent: SignedOutEvent())],
),
); );
}, },
build: () => TestAuthenticationCubit( build: () => TestAuthenticationCubit(

View File

@ -27,7 +27,7 @@ void main() {
const AuthenticationState<void> state = const AuthenticationState<void> state =
AuthenticationState.unauthenticated(); AuthenticationState.unauthenticated();
expect(state.status, AuthenticationStatus.unauthenticated); expect(state.status, AuthenticationStatus.unauthenticated);
expect(state.wrapper, null); expect(state.session, null);
}); });
}); });
@ -36,13 +36,12 @@ void main() {
final MockAccount account = MockAccount(); final MockAccount account = MockAccount();
final AuthenticationState<void> state = final AuthenticationState<void> state =
AuthenticationState.authenticated( AuthenticationState.authenticated(
SessionWrapper<void>( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), SignedInEvent(account: account),
session: Session(account: account),
), ),
); );
expect(state.status, AuthenticationStatus.authenticated); expect(state.status, AuthenticationStatus.authenticated);
expect(state.wrapper?.session?.account, account); expect(state.session?.account, account);
}); });
}); });
@ -52,14 +51,14 @@ void main() {
const String extra = 'AwesomeExtraData'; const String extra = 'AwesomeExtraData';
final AuthenticationState<String> state = final AuthenticationState<String> state =
AuthenticationState.authenticated( AuthenticationState.authenticated(
SessionWrapper<String>( AuthenticationSession.fromEvent(
event: SignedInEvent(account: account), SignedInEvent(account: account),
session: Session(account: account, data: extra), data: extra,
), ),
); );
expect(state.status, AuthenticationStatus.authenticated); expect(state.status, AuthenticationStatus.authenticated);
expect(state.wrapper?.session?.account, account); expect(state.session?.account, account);
expect(state.wrapper?.session?.data, extra); expect(state.session?.data, extra);
}); });
}); });
}); });

View File

@ -43,13 +43,24 @@ void main() {
when(() => authenticationRepository.sessionStream()).thenAnswer( when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable([ (_) => Stream.fromIterable([
SessionWrapper<int>( AuthenticationSession.fromEvent(
event: SignedInFromCacheEvent(account: account), SignedInFromCacheEvent(account: account),
session: Session<int>(account: account, data: 10), data: 10,
) )
]), ]),
); );
when(
() => authenticationRepository.currentSession(),
).thenAnswer(
(_) async => Ok(
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: account),
data: 10,
),
),
);
when( when(
() => authenticationRepository.refresh(), () => authenticationRepository.refresh(),
).thenAnswer((_) async => Ok(account)); ).thenAnswer((_) async => Ok(account));