Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .github/workflows/main.yaml

This file was deleted.

Binary file added assets/letter_l.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 32 additions & 3 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import 'package:flow_builder/flow_builder.dart';
import 'package:flutter/material.dart';
import 'package:learn/counter/counter.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:learn/authentication/authentication.dart';
import 'package:learn/l10n/l10n.dart';
import 'package:learn/repositories/src/authentication_repository/authentication_repository.dart';
import 'package:learn/routes/routes.dart';

class App extends StatelessWidget {
const App({super.key});
const App({
super.key,
required AuthenticationRepository authenticationRepository,
}) : _authenticationRepository = authenticationRepository;

final AuthenticationRepository _authenticationRepository;

@override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: _authenticationRepository,
child: BlocProvider(
create: (_) => AuthenticationBloc(
authenticationRepository: _authenticationRepository,
),
child: const AppView(),
),
);
}
}

class AppView extends StatelessWidget {
const AppView({super.key});

@override
Widget build(BuildContext context) {
Expand All @@ -16,7 +42,10 @@ class App extends StatelessWidget {
),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const CounterPage(),
home: FlowBuilder<AuthStatus>(
state: context.select((AuthenticationBloc bloc) => bloc.state.status),
onGeneratePages: onGenerateAppViewPages,
),
);
}
}
5 changes: 5 additions & 0 deletions lib/authentication/authentication.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export 'bloc/authentication_bloc.dart';
export 'view/login_form.dart';
export 'view/login_page.dart';
export 'view/signup_form.dart';
export 'view/signup_page.dart';
50 changes: 50 additions & 0 deletions lib/authentication/bloc/authentication_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:learn/repositories/repositories.dart';

part 'authentication_event.dart';
part 'authentication_state.dart';

class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc({
required AuthenticationRepository authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
super(
authenticationRepository.currentUser.isNotEmpty
? AuthenticationState.authenticated(
authenticationRepository.currentUser)
: const AuthenticationState.unauthenticated(),
) {
on<_AppUserChanged>(_onUserChanged);
on<AppLogoutRequested>(_onLogoutRequested);
_userSubscription = _authenticationRepository.user.listen(
(user) => add(_AppUserChanged(user)),
);
}

final AuthenticationRepository _authenticationRepository;
late final StreamSubscription<User> _userSubscription;

void _onUserChanged(
_AppUserChanged event, Emitter<AuthenticationState> emit) {
emit(
event.user.isNotEmpty
? AuthenticationState.authenticated(event.user)
: const AuthenticationState.unauthenticated(),
);
}

void _onLogoutRequested(
AppLogoutRequested event, Emitter<AuthenticationState> emit) {
unawaited(_authenticationRepository.logOut());
}

@override
Future<void> close() {
_userSubscription.cancel();
return super.close();
}
}
15 changes: 15 additions & 0 deletions lib/authentication/bloc/authentication_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
part of 'authentication_bloc.dart';

abstract class AuthenticationEvent {
const AuthenticationEvent();
}

class AppLogoutRequested extends AuthenticationEvent {
const AppLogoutRequested();
}

class _AppUserChanged extends AuthenticationEvent {
const _AppUserChanged(this.user);

final User user;
}
26 changes: 26 additions & 0 deletions lib/authentication/bloc/authentication_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
part of 'authentication_bloc.dart';

enum AuthStatus {
authenticated,
unauthenticated,
inProgress,
}

class AuthenticationState extends Equatable {
const AuthenticationState._({
required this.status,
this.user = User.empty,
});

const AuthenticationState.authenticated(User user)
: this._(status: AuthStatus.authenticated, user: user);

const AuthenticationState.unauthenticated()
: this._(status: AuthStatus.unauthenticated);

final AuthStatus status;
final User user;

@override
List<Object> get props => [status, user];
}
72 changes: 72 additions & 0 deletions lib/authentication/cubit/login_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:formz/formz.dart';
import 'package:learn/authentication/models/email.dart';
import 'package:learn/authentication/models/password.dart';
import 'package:learn/repositories/repositories.dart';

part 'login_state.dart';

class LoginCubit extends Cubit<LoginState> {
LoginCubit(this._authenticationRepository) : super(const LoginState());

final AuthenticationRepository _authenticationRepository;

void emailChanged(String value) {
final email = Email.dirty(value);
emit(
state.copyWith(
email: email,
status: Formz.validate([email, state.password]),
),
);
}

void passwordChanged(String value) {
final password = Password.dirty(value);
emit(
state.copyWith(
password: password,
status: Formz.validate([state.email, password]),
),
);
}

Future<void> logInWithCredentials() async {
if (!state.status.isValidated) return;
emit(state.copyWith(status: FormzStatus.submissionInProgress));
try {
await _authenticationRepository.logInWithEmailAndPassword(
email: state.email.value,
password: state.password.value,
);
emit(state.copyWith(status: FormzStatus.submissionSuccess));
} on LogInWithEmailAndPasswordFailure catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: FormzStatus.submissionFailure,
),
);
} catch (_) {
emit(state.copyWith(status: FormzStatus.submissionFailure));
}
}

Future<void> logInWithGoogle() async {
emit(state.copyWith(status: FormzStatus.submissionInProgress));
try {
await _authenticationRepository.logInWithGoogle();
emit(state.copyWith(status: FormzStatus.submissionSuccess));
} on LogInWithGoogleFailure catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: FormzStatus.submissionFailure,
),
);
} catch (_) {
emit(state.copyWith(status: FormzStatus.submissionFailure));
}
}
}
32 changes: 32 additions & 0 deletions lib/authentication/cubit/login_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
part of 'login_cubit.dart';

class LoginState extends Equatable {
const LoginState({
this.email = const Email.pure(),
this.password = const Password.pure(),
this.status = FormzStatus.pure,
this.errorMessage,
});

final Email email;
final Password password;
final FormzStatus status;
final String? errorMessage;

@override
List<Object> get props => [email, password, status];

LoginState copyWith({
Email? email,
Password? password,
FormzStatus? status,
String? errorMessage,
}) {
return LoginState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}
86 changes: 86 additions & 0 deletions lib/authentication/cubit/signup_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:formz/formz.dart';
import 'package:learn/authentication/models/confirmed_password.dart';
import 'package:learn/authentication/models/email.dart';
import 'package:learn/authentication/models/password.dart';
import 'package:learn/repositories/repositories.dart';

part 'signup_state.dart';

class SignUpCubit extends Cubit<SignUpState> {
SignUpCubit(this._authenticationRepository) : super(const SignUpState());

final AuthenticationRepository _authenticationRepository;

void emailChanged(String value) {
final email = Email.dirty(value);
emit(
state.copyWith(
email: email,
status: Formz.validate([
email,
state.password,
state.confirmedPassword,
]),
),
);
}

void passwordChanged(String value) {
final password = Password.dirty(value);
final confirmedPassword = ConfirmedPassword.dirty(
password: password.value,
value: state.confirmedPassword.value,
);
emit(
state.copyWith(
password: password,
confirmedPassword: confirmedPassword,
status: Formz.validate([
state.email,
password,
confirmedPassword,
]),
),
);
}

void confirmedPasswordChanged(String value) {
final confirmedPassword = ConfirmedPassword.dirty(
password: state.password.value,
value: value,
);
emit(
state.copyWith(
confirmedPassword: confirmedPassword,
status: Formz.validate([
state.email,
state.password,
confirmedPassword,
]),
),
);
}

Future<void> signUpFormSubmitted() async {
if (!state.status.isValidated) return;
emit(state.copyWith(status: FormzStatus.submissionInProgress));
try {
await _authenticationRepository.signUp(
email: state.email.value,
password: state.password.value,
);
emit(state.copyWith(status: FormzStatus.submissionSuccess));
} on SignUpWithEmailAndPasswordFailure catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: FormzStatus.submissionFailure,
),
);
} catch (_) {
emit(state.copyWith(status: FormzStatus.submissionFailure));
}
}
}
38 changes: 38 additions & 0 deletions lib/authentication/cubit/signup_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
part of 'signup_cubit.dart';

enum ConfirmPasswordValidationError { invalid }

class SignUpState extends Equatable {
const SignUpState({
this.email = const Email.pure(),
this.password = const Password.pure(),
this.confirmedPassword = const ConfirmedPassword.pure(),
this.status = FormzStatus.pure,
this.errorMessage,
});

final Email email;
final Password password;
final ConfirmedPassword confirmedPassword;
final FormzStatus status;
final String? errorMessage;

@override
List<Object> get props => [email, password, confirmedPassword, status];

SignUpState copyWith({
Email? email,
Password? password,
ConfirmedPassword? confirmedPassword,
FormzStatus? status,
String? errorMessage,
}) {
return SignUpState(
email: email ?? this.email,
password: password ?? this.password,
confirmedPassword: confirmedPassword ?? this.confirmedPassword,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
);
}
}
Loading