diff --git a/lib/application/auth/auth_bloc.dart b/lib/application/auth/auth_bloc.dart new file mode 100644 index 0000000..1b7bed5 --- /dev/null +++ b/lib/application/auth/auth_bloc.dart @@ -0,0 +1,49 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../domain/auth/auth.dart'; + +part 'auth_event.dart'; +part 'auth_state.dart'; +part 'auth_bloc.freezed.dart'; + +@injectable +class AuthBloc extends Bloc { + final IAuthRepository _repository; + AuthBloc(this._repository) : super(AuthState.initial()) { + on(_onAuthEvent); + } + + Future _onAuthEvent(AuthEvent event, Emitter emit) { + return event.map( + fetchCurrentUser: (e) async { + emit(state.copyWith(failureOption: none())); + + final token = await _repository.hasToken(); + + final failureOrAuth = await _repository.currentUser(); + + failureOrAuth.fold( + (f) => emit( + state.copyWith( + failureOption: optionOf(f), + status: token + ? AuthStatus.authenticated() + : AuthStatus.unauthenticated(), + ), + ), + (user) => emit( + state.copyWith( + user: user, + status: token + ? AuthStatus.authenticated() + : AuthStatus.unauthenticated(), + ), + ), + ); + }, + ); + } +} diff --git a/lib/application/auth/auth_bloc.freezed.dart b/lib/application/auth/auth_bloc.freezed.dart new file mode 100644 index 0000000..7fd3e02 --- /dev/null +++ b/lib/application/auth/auth_bloc.freezed.dart @@ -0,0 +1,806 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$AuthEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetchCurrentUser, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_FetchCurrentUser value) fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_FetchCurrentUser value)? fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_FetchCurrentUser value)? fetchCurrentUser, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthEventCopyWith<$Res> { + factory $AuthEventCopyWith(AuthEvent value, $Res Function(AuthEvent) then) = + _$AuthEventCopyWithImpl<$Res, AuthEvent>; +} + +/// @nodoc +class _$AuthEventCopyWithImpl<$Res, $Val extends AuthEvent> + implements $AuthEventCopyWith<$Res> { + _$AuthEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchCurrentUserImplCopyWith<$Res> { + factory _$$FetchCurrentUserImplCopyWith( + _$FetchCurrentUserImpl value, + $Res Function(_$FetchCurrentUserImpl) then, + ) = __$$FetchCurrentUserImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchCurrentUserImplCopyWithImpl<$Res> + extends _$AuthEventCopyWithImpl<$Res, _$FetchCurrentUserImpl> + implements _$$FetchCurrentUserImplCopyWith<$Res> { + __$$FetchCurrentUserImplCopyWithImpl( + _$FetchCurrentUserImpl _value, + $Res Function(_$FetchCurrentUserImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchCurrentUserImpl implements _FetchCurrentUser { + const _$FetchCurrentUserImpl(); + + @override + String toString() { + return 'AuthEvent.fetchCurrentUser()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchCurrentUserImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() fetchCurrentUser, + }) { + return fetchCurrentUser(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetchCurrentUser, + }) { + return fetchCurrentUser?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetchCurrentUser, + required TResult orElse(), + }) { + if (fetchCurrentUser != null) { + return fetchCurrentUser(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_FetchCurrentUser value) fetchCurrentUser, + }) { + return fetchCurrentUser(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_FetchCurrentUser value)? fetchCurrentUser, + }) { + return fetchCurrentUser?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_FetchCurrentUser value)? fetchCurrentUser, + required TResult orElse(), + }) { + if (fetchCurrentUser != null) { + return fetchCurrentUser(this); + } + return orElse(); + } +} + +abstract class _FetchCurrentUser implements AuthEvent { + const factory _FetchCurrentUser() = _$FetchCurrentUserImpl; +} + +/// @nodoc +mixin _$AuthState { + User get user => throw _privateConstructorUsedError; + AuthStatus get status => throw _privateConstructorUsedError; + Option get failureOption => throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AuthStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStateCopyWith<$Res> { + factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = + _$AuthStateCopyWithImpl<$Res, AuthState>; + @useResult + $Res call({ + User user, + AuthStatus status, + Option failureOption, + bool isFetching, + }); + + $UserCopyWith<$Res> get user; + $AuthStatusCopyWith<$Res> get status; +} + +/// @nodoc +class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> + implements $AuthStateCopyWith<$Res> { + _$AuthStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? user = null, + Object? status = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserCopyWith<$Res> get user { + return $UserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AuthStatusCopyWith<$Res> get status { + return $AuthStatusCopyWith<$Res>(_value.status, (value) { + return _then(_value.copyWith(status: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AuthStateImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthStateImplCopyWith( + _$AuthStateImpl value, + $Res Function(_$AuthStateImpl) then, + ) = __$$AuthStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + User user, + AuthStatus status, + Option failureOption, + bool isFetching, + }); + + @override + $UserCopyWith<$Res> get user; + @override + $AuthStatusCopyWith<$Res> get status; +} + +/// @nodoc +class __$$AuthStateImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateImpl> + implements _$$AuthStateImplCopyWith<$Res> { + __$$AuthStateImplCopyWithImpl( + _$AuthStateImpl _value, + $Res Function(_$AuthStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? user = null, + Object? status = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _$AuthStateImpl( + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$AuthStateImpl extends _AuthState { + const _$AuthStateImpl({ + required this.user, + this.status = const AuthStatus.initial(), + required this.failureOption, + this.isFetching = false, + }) : super._(); + + @override + final User user; + @override + @JsonKey() + final AuthStatus status; + @override + final Option failureOption; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'AuthState(user: $user, status: $status, failureOption: $failureOption, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateImpl && + (identical(other.user, user) || other.user == user) && + (identical(other.status, status) || other.status == status) && + (identical(other.failureOption, failureOption) || + other.failureOption == failureOption) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => + Object.hash(runtimeType, user, status, failureOption, isFetching); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + __$$AuthStateImplCopyWithImpl<_$AuthStateImpl>(this, _$identity); +} + +abstract class _AuthState extends AuthState { + const factory _AuthState({ + required final User user, + final AuthStatus status, + required final Option failureOption, + final bool isFetching, + }) = _$AuthStateImpl; + const _AuthState._() : super._(); + + @override + User get user; + @override + AuthStatus get status; + @override + Option get failureOption; + @override + bool get isFetching; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$AuthStatus { + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStatusCopyWith<$Res> { + factory $AuthStatusCopyWith( + AuthStatus value, + $Res Function(AuthStatus) then, + ) = _$AuthStatusCopyWithImpl<$Res, AuthStatus>; +} + +/// @nodoc +class _$AuthStatusCopyWithImpl<$Res, $Val extends AuthStatus> + implements $AuthStatusCopyWith<$Res> { + _$AuthStatusCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$AuthenticatedImplCopyWith<$Res> { + factory _$$AuthenticatedImplCopyWith( + _$AuthenticatedImpl value, + $Res Function(_$AuthenticatedImpl) then, + ) = __$$AuthenticatedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$AuthenticatedImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$AuthenticatedImpl> + implements _$$AuthenticatedImplCopyWith<$Res> { + __$$AuthenticatedImplCopyWithImpl( + _$AuthenticatedImpl _value, + $Res Function(_$AuthenticatedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$AuthenticatedImpl implements _Authenticated { + const _$AuthenticatedImpl(); + + @override + String toString() { + return 'AuthStatus.authenticated()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$AuthenticatedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return authenticated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return authenticated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return authenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return authenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(this); + } + return orElse(); + } +} + +abstract class _Authenticated implements AuthStatus { + const factory _Authenticated() = _$AuthenticatedImpl; +} + +/// @nodoc +abstract class _$$UnauthenticatedImplCopyWith<$Res> { + factory _$$UnauthenticatedImplCopyWith( + _$UnauthenticatedImpl value, + $Res Function(_$UnauthenticatedImpl) then, + ) = __$$UnauthenticatedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnauthenticatedImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$UnauthenticatedImpl> + implements _$$UnauthenticatedImplCopyWith<$Res> { + __$$UnauthenticatedImplCopyWithImpl( + _$UnauthenticatedImpl _value, + $Res Function(_$UnauthenticatedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnauthenticatedImpl implements _Unauthenticated { + const _$UnauthenticatedImpl(); + + @override + String toString() { + return 'AuthStatus.unauthenticated()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnauthenticatedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return unauthenticated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return unauthenticated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return unauthenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return unauthenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(this); + } + return orElse(); + } +} + +abstract class _Unauthenticated implements AuthStatus { + const factory _Unauthenticated() = _$UnauthenticatedImpl; +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, + $Res Function(_$InitialImpl) then, + ) = __$$InitialImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, + $Res Function(_$InitialImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'AuthStatus.initial()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$InitialImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements AuthStatus { + const factory _Initial() = _$InitialImpl; +} diff --git a/lib/application/auth/auth_event.dart b/lib/application/auth/auth_event.dart new file mode 100644 index 0000000..6c3c293 --- /dev/null +++ b/lib/application/auth/auth_event.dart @@ -0,0 +1,6 @@ +part of 'auth_bloc.dart'; + +@freezed +class AuthEvent with _$AuthEvent { + const factory AuthEvent.fetchCurrentUser() = _FetchCurrentUser; +} diff --git a/lib/application/auth/auth_state.dart b/lib/application/auth/auth_state.dart new file mode 100644 index 0000000..483740d --- /dev/null +++ b/lib/application/auth/auth_state.dart @@ -0,0 +1,26 @@ +part of 'auth_bloc.dart'; + +@freezed +class AuthState with _$AuthState { + const AuthState._(); + + const factory AuthState({ + required User user, + @Default(AuthStatus.initial()) AuthStatus status, + required Option failureOption, + @Default(false) bool isFetching, + }) = _AuthState; + + factory AuthState.initial() => + AuthState(user: User.empty(), failureOption: none()); + + bool get isAuthenticated => status == const AuthStatus.authenticated(); + bool get isInitial => status == const AuthStatus.initial(); +} + +@freezed +sealed class AuthStatus with _$AuthStatus { + const factory AuthStatus.authenticated() = _Authenticated; + const factory AuthStatus.unauthenticated() = _Unauthenticated; + const factory AuthStatus.initial() = _Initial; +} diff --git a/lib/domain/auth/repositories/i_auth_repository.dart b/lib/domain/auth/repositories/i_auth_repository.dart index 2cdc9f0..22adf9d 100644 --- a/lib/domain/auth/repositories/i_auth_repository.dart +++ b/lib/domain/auth/repositories/i_auth_repository.dart @@ -5,4 +5,6 @@ abstract class IAuthRepository { required String email, required String password, }); + Future hasToken(); + Future> currentUser(); } diff --git a/lib/infrastructure/auth/repositories/auth_repository.dart b/lib/infrastructure/auth/repositories/auth_repository.dart index 9faa273..4682b67 100644 --- a/lib/infrastructure/auth/repositories/auth_repository.dart +++ b/lib/infrastructure/auth/repositories/auth_repository.dart @@ -42,4 +42,20 @@ class AuthRepository implements IAuthRepository { return left(const AuthFailure.unexpectedError()); } } + + @override + Future> currentUser() async { + try { + User user = await _localDataProvider.currentUser(); + return right(user); + } catch (e, s) { + log('currentUserError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future hasToken() async { + return await _localDataProvider.hasToken(); + } } diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 4a98ad4..c5bfd6f 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -9,6 +9,7 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:apskel_owner_flutter/application/auth/auth_bloc.dart' as _i945; import 'package:apskel_owner_flutter/application/auth/login_form/login_form_bloc.dart' as _i775; import 'package:apskel_owner_flutter/application/language/language_bloc.dart' @@ -91,6 +92,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i775.LoginFormBloc>( () => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()), ); + gh.factory<_i945.AuthBloc>( + () => _i945.AuthBloc(gh<_i49.IAuthRepository>()), + ); return this; } } diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index 1dda198..9ff9ce6 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../application/auth/auth_bloc.dart'; import '../application/language/language_bloc.dart'; import '../common/theme/theme.dart'; import '../common/constant/app_constant.dart'; @@ -21,8 +22,11 @@ class _AppWidgetState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt(), + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), + ], child: BlocBuilder( builder: (context, state) { return MaterialApp.router( diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart index a7706e8..46024f7 100644 --- a/lib/presentation/pages/splash/splash_page.dart +++ b/lib/presentation/pages/splash/splash_page.dart @@ -1,7 +1,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import '../../../application/auth/auth_bloc.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../injection.dart'; @@ -77,15 +79,10 @@ class _SplashPageState extends State with TickerProviderStateMixin { // Navigate to home screen after all animations complete await Future.delayed(const Duration(milliseconds: 2000)); if (mounted) { - _navigateToHome(); + context.read().add(const AuthEvent.fetchCurrentUser()); } } - void _navigateToHome() { - // Uncomment dan sesuaikan dengan route yang ada - context.router.replace(const LoginRoute()); - } - @override void dispose() { _logoController.dispose(); @@ -97,80 +94,97 @@ class _SplashPageState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final packageInfo = getIt(); - return Scaffold( - body: AnimatedBuilder( - animation: Listenable.merge([ - _logoController, - _versionController, - _backgroundController, - ]), - builder: (context, child) { - // Clamp values to prevent opacity errors - final logoOpacity = _logoAnimation.value.clamp(0.0, 1.0); - final versionOpacity = _versionAnimation.value.clamp(0.0, 1.0); - final backgroundOpacity = _backgroundAnimation.value.clamp(0.0, 1.0); + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.isAuthenticated) { + context.router.replace(const MainRoute()); + } else { + context.router.replace(const LoginRoute()); + } + }, + child: Scaffold( + body: AnimatedBuilder( + animation: Listenable.merge([ + _logoController, + _versionController, + _backgroundController, + ]), + builder: (context, child) { + // Clamp values to prevent opacity errors + final logoOpacity = _logoAnimation.value.clamp(0.0, 1.0); + final versionOpacity = _versionAnimation.value.clamp(0.0, 1.0); + final backgroundOpacity = _backgroundAnimation.value.clamp( + 0.0, + 1.0, + ); - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - AppColor.primaryWithOpacity(backgroundOpacity), - AppColor.primaryWithOpacity(backgroundOpacity * 0.8), + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColor.primaryWithOpacity(backgroundOpacity), + AppColor.primaryWithOpacity(backgroundOpacity * 0.8), + ], + ), + ), + child: Stack( + children: [ + // Logo di tengah + Center( + child: Transform.scale( + scale: logoOpacity, + child: Opacity( + opacity: logoOpacity, + child: Container( + width: 150, + height: 150, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(0, 15), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + AppValue.radius, + ), + child: Assets.images.logo.image(fit: BoxFit.cover), + ), + ), + ), + ), + ), + + // Version di bagian bawah + Positioned( + bottom: 60, + left: 0, + right: 0, + child: Transform.translate( + offset: Offset(0, 20 * (1 - versionOpacity)), + child: Opacity( + opacity: versionOpacity, + child: Text( + '${context.lang.version} ${packageInfo.version}+${packageInfo.buildNumber}', + style: AppStyle.md.copyWith( + color: AppColor.textLight, + ), + textAlign: TextAlign.center, + ), + ), + ), + ), ], ), - ), - child: Stack( - children: [ - // Logo di tengah - Center( - child: Transform.scale( - scale: logoOpacity, - child: Opacity( - opacity: logoOpacity, - child: Container( - width: 150, - height: 150, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 30, - offset: const Offset(0, 15), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(AppValue.radius), - child: Assets.images.logo.image(fit: BoxFit.cover), - ), - ), - ), - ), - ), - - // Version di bagian bawah - Positioned( - bottom: 60, - left: 0, - right: 0, - child: Transform.translate( - offset: Offset(0, 20 * (1 - versionOpacity)), - child: Opacity( - opacity: versionOpacity, - child: Text( - '${context.lang.version} ${packageInfo.version}+${packageInfo.buildNumber}', - style: AppStyle.md.copyWith(color: AppColor.textLight), - textAlign: TextAlign.center, - ), - ), - ), - ), - ], - ), - ); - }, + ); + }, + ), ), ); }